Skip to content

Latest commit

 

History

History
 
 

day-027

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Day 27: Project 5, Part One

Follow along at https://www.hackingwithswift.com/100/27.

📒 Field Notes

This day covers the first part of Project 5: Word Scramble in Hacking with Swift.

I have a separate repository where I've been creating projects alongside the material in the book. And you can find Project 5 here. However, I also copied it over to this day's folder so I can extend from where I left off.

With that in mind, Day 27 focuses on several specific topics:

  • Capture lists in Swift: What’s the difference between weak, strong, and unowned references?
  • Reading from disk: contentsOfFile
  • Entering words with UIAlertController

Capture lists in Swift

Just as important as understanding closures themselves, understanding how closure capture lists retain their values is a fundamental part of building applications in Swift. Paul Hudson's recent article for this day provides a thorough breakdown of weak, strong, and unowned references, and is well worth the read.

In short everything is strong by default. If we don't specify weak or unowned, Swift will assume that we don't want a reference to be cleaned up unexpectedly.

Specifying weak is essentially acknowledging that we're okay with a reference being cleaned up at some point, and that our code inside the closure will treat it like an Optional:

func makeSing() -> () -> Void {
    let singer = Singer(name: "Taylor")

    return { [weak singer] in
        singer?.sing()
    }
}

Most of the time, weak is a sane — and accurate — designation. But we can get closer to the edge with unowned. This tells the compiler that we're okay with the object being cleaned up at some point, because we're sure it won't be cleaned up before our code will use it. Okay... if you say so... 😎:

func makeSing() -> () -> Void {
    let singer = Singer(name: "Taylor")

    return  { [unowned singer] in
        singer.sing()
    }
}

On one hand, we get a terser syntax — we don't have to treat the captured value as an Optional, much like implicit unwrapping — but it also means are code will crash if the reference is, indeed, nil (as is the case for the example above, where singer won't live beyond the makeSing function).

Reading from disk: contentsOfFile

After using Bundle.main.path(forResource:ofType:) to find the path of our words file our Bundle (which, I've found, is somewhat magical when getting started with iOS — so the more practice, the better), we can get a giant string of the words by using String.contentsOfFile:

if let startWords = try? String(contentsOfFile: pathToStartWords) {
  allWords = startWords.components(separatedBy: "\n")
}

So declarative. So expressive. So powerful. This is the kind of thing that feels like cheating 😀.

Entering words with UIAlertController

The action that handles the user pressing our "Add" button needs to display an instance of UIAlertController — which, itself, contains a text field for the answer and a submit button.

When the submit button is pressed, we need another action to handle the value of the text in the UIAlertController instance before iOS gets rid of it.

And that's where avoiding strong reference cycles becomes important. Because iOS will get rid of the UIAlertController instance at some point, we need to make sure our closure for handling text input — which lives on the UIAlertController instance that's being destroyed — doesn't try to stay strongly attached.

let submitAction = UIAlertAction(title: "Submit", style: .default) { [weak self, weak alertController] _ in
    guard let answer = alertController?.textFields?[0].text else {
        return
    }

    self?.handleSubmit(answer)
}