WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Day 40 - A few questions

Forums > 100 Days of SwiftUI

Hello everyone!

I would like to know a few things. Here is all the code.

extension Bundle {
    func decode(_ file: String) -> [String : Astronaut] {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle")
        }

        let decoder = JSONDecoder()

        guard let loaded = try? decoder.decode([String: Astronaut].self, from: data) else {
            fatalError("Failed to decode \(file) forom bundle")
        }

        return loaded
    }
}
  1. Why is "self.url" here ?
guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle")
        }
  1. What happens if I delete "self." ?
  2. In the following examples, we use "try?" But in this example, "try?" did not work. I know that:

If we use the try? keyword and an error is thrown, the error is handled by turning it into an optional value. This means that there is no need to wrap the throwing method call in a do-catch statement.

Since it throws an error here, why can't I use "try?" and why does it work without "try?"

  1. When we load JSON into our content view. We add this property:
let astronauts = Bundle.main.decode("astronauts.json")

I don't know where that ".main" came from.

  1. Why in "let the astronauts" we use - "String: Astronaut" . But in " let missions" there is only -" Missions", what is it caused?
 let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
 let missions: [Mission] = Bundle.main.decode("missions.json")

Forgive me for my English. I realize I'm terrible at writing in English

   

Why is "self.url" here ?

self refers to the instance of Bundle that you are calling it on. So, for instance, in the code:

Bundle.main.decode("missions.json")

self would refer to the Bundle indicated by Bundle.main.

What happens if I delete "self." ?

Nothing, really. url(forResource:withExtension:) is a method on Bundle. It will get called on whatever Bundle you are calling decode(_:) on whether or not the self. is there. It's just clearer what you are doing with the self. added.

Since it throws an error here, why can't I use "try?" and why does it work without "try?"

I don't understand what you are asking. You need some form of try here for the function to work. If you use try, you will need to deal with error throwing; if you use try? you will need to deal with the Optional.

I don't know where that ".main" came from.

main is a property of the Bundle class. It returns "the bundle directory that contains the current executable." See the docs for more info.

   

I do not know how to explain it. I will try to write clearer.

 guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle")
        }

I don't understand why in "guard let data" and "guard let loaded", we use "try?" and not in "guard let url". Only in this case, we don't use "try?", but why? I don't understand why once is "try?" and once it is not. Since it looks like the end result is the same

   

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Hi! If I am not mistaken, those two functions with try you mentioned in your code are throwing functions i.e. decoder and data might throw errors. So for throwing functions you have to use try before them. As for url(forResource:) you don't need it as this is not throwing function.

try This must be written before calling all functions that might throw errors, and is a visual signal to developers that regular code execution will be interrupted if an error happens. Paul explains it here

   

Bundle.url(forResource:withExtension:) returns an Optional, so we can use it with guard let.

Data.init(contentsOf:options:) (which just shows up as Data(contentsOf:) in this example since the options parameter has a default value of []) and JSONDecoder.decode(_:from:) are both throwing functions, as @ygeras points out.

Throwing functions can be handled in a do { } catch { } block using the try keyword. Or they can have the result turned into an Optional using the try? and try! keywords; if there is no error, we get a result value, if there is an error we get nil (with the added "feature" that try! will crash if nil is returned).

So since try? gives us an Optional, we can use those method calls in a guard let construction.

That's why url(forResource:withExtension:) doesn't need a try? while the other two calls do but both can be used with guard let

   

Developer Responsibility

It’s your responsibility, as a developer, to know about all these possibilities, and code your solutions effectively.

Vanilla Methods

Developers (you, me, @twoStraws, Apple) may write methods or functions to perform a task. Some of these tasks do a lot of work, but do not return any data. Other functions may perform work then return data to you as an Int, Bool, String, Complex View, Array, or any other legitimate Swift data type.

.uppercased() only works with Strings, not with Integers. It’s your responsibility to ensure you add this only to Strings! Of course the Swift compiler will help you here.

// Paste into Playgrounds
// 1. Simple return types.
let player = "Mario"
player.uppercased() // Function returns a string.

let theUltimateAnswer = 42
theUltimateAnswer.uppercased()  // No. The compiler is not happy with this.

Methods Returning Optionals

Still other functions may return Optionals. (Yes, these are legitimate Swift data types and should be included above, but are different enough that I’ve called them out.) If you’re still unsure about Optionals, STOP HERE and REVIEW OPTIONALS. In short, an Optional may return data to you, or it may return NOTHING.

// 2. Optional Return Types
// Return an Optional Integer
let notableIntegers = [ 7, 42, 256, 1024 ] // NOTE: does not contain the prime number 997
let bigPrime        = notableIntegers.firstIndex(of: 997) // Returns an Optional. Returns nil
let ultimateAnswer  = notableIntegers.firstIndex(of:  42) // Returns an Optional. Returns 1

Throwing Methods

Finally, some methods may return data to you if everything works out ok. But if there’s a problem, the method will throw an error. You may still get data back (typically, in the form of an error message.) But the method itself terminates by THROWING an error.

// 3. Func that throws an error.
enum ImportantNumberError: Error { case notJennysNumber }
func doDangerousThing(importantNumber: Int) throws -> Int {  
    if importantNumber == 8675309 { // Jenny, I've got your number!
        return importantNumber
    } else {
        throw ImportantNumberError.notJennysNumber // Disaster!  Throw an error.
    }
}

// Because your method could possibly THROW an error, you must TRY to run it.
// You are not obligated to catch errors, but hey, it might be important to your financial success!

do {
    try doDangerousThing(importantNumber: 5551212)  // Try to run the method.
} catch {
    print(error) // Yikes! The method found an error. What  are you going to do?
}

let response       = try? doDangerousThing(importantNumber: 13)       // Returns nil. Ignores the error.
let properResponse = try? doDangerousThing(importantNumber: 8675309)  // Returns 8675309. No error thrown.
let someResponse   = doDangerousThing(importantNumber: 13)            // Method THROWS. Compiler won't allow this.

1      

After analyzing your answers, I finally understood why there is no "try?". But I have one more question.

Why in "let astronauts" we use - "String: Astronaut" . But in " let missions" there is only -" Missions", without "String: ...." what is it caused?

 let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
 let missions: [Mission] = Bundle.main.decode("missions.json")

Thank you in advance for your response :)

   

[String:Astronaut] is a Dictionary, with a key type of String and a value type of Astronaut.

[Mission] is an Array with an element type of Mission.

Review Day 3 for more info on the difference between the two.

   

I still don't understand it. I have two structures here

struct Mission: Codable, Identifiable {
    struct CrewRole: Codable {
        let name: String
        let role: String
    }

    let id: Int
    let launchDate: String?
    let crew: [CrewRole]
    let description: String
}

And the second structure

struct Astronaut: Codable, Identifiable {
    let id: String
    let name: String
    let description: String
}

We use the same decoder for them

import Foundation

extension Bundle {
    func decode<T: Codable>(_ file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle")
        }

        let decoder = JSONDecoder()

        guard let loaded = try? decoder.decode(T.self, from: data) else {
            fatalError("Failed to decode \(file) forom bundle")
        }

        return loaded
    }
}

Then why "let astronauts" is a Dictionary, and " let missions" is Array. I know the differences between Dictionary and Array.

 let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
 let missions: [Mission] = Bundle.main.decode("missions.json")

Thanks for the answer :)

   

This is the power of generics. Here, we are using the same function to decode [String:Astronaut] and [Mission] even though those are two different types.

Take a look at the function declaration for Bundle.decode:

func decode<T: Codable>(_ file: String) -> T

T is a generic placeholder. It indicates that we can substitute in any type we want as long as it satisifes the contraints put on it here: <T: Codable>. So this tells us that we can substitute in any type we want as long as that type is Codable.

Arrays and Dictionaries are Codable by default, as long as their members are Codable. Now take a look at the types we are working with in this project:

struct Astronaut: Codable, Identifiable
struct Mission: Codable, Identifiable

String is Codable by default, and Astronaut is marked as Codable, so a dictionary [String: Astronaut] satisifes our generic constraint.

Since Mission is marked as Codable, then an array of Mission is Codable. So [Mission] satisfies our generic constraint.

This means we can use Bundle.decode with both types.

Using a generic function is essentially like having different functions for each type that you call it with, without having to actually write all those different functions.

Well, how does Bundle.decode know what type we actually want to use? If you look again at the function declaration, you can see that the return type is specified as T, so when you use the function, whatever return type you tell the compiler you are expecting will determine what type T corresponds to. So if we substitute in our types for the generic placeholder, we get:

//calling decode with a return type of [String: Astronaut]
let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
//essentially gives us this function for free:
func decode(_ file: String) -> [String: Astronaut]

//calling decode with a return type of [Mission]
let missions: [Mission] = Bundle.main.decode("missions.json")
//essentially gives us this function for free:
func decode(_ file: String) -> [Mission]

And if you look into the decode function, you can see another place where the T placeholder is being used:

guard let loaded = try? decoder.decode(T.self, from: data)

Once again, if we sub in our types:

//calling `Bundle.decode` with a return type of [String: Astronaut] gives us:
guard let loaded = try? decoder.decode([String: Astronaut].self, from: data)

//calling `Bundle.decode` with a return type of [Mission] gives us:
guard let loaded = try? decoder.decode([Mission].self, from: data)

Okay, but how do we know we need [String: Astronaut] and [Mission] in the first place? Because those match the structure of the JSON files we are reading out of our bundle.

   

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.