lelephant
lelephant

Reputation: 95

Swift unwraps value as nil when in closure but successfully unwraps elsewhere in code?

So here is my code

func retrieveAndAssignHighScoreFromCloudKit() {
    let high = highScore
    database.fetchRecordWithID(getRecordID(high), completionHandler: { [weak self]
        (record: CKRecord?, error: NSError?) in
        if error != nil {
            print(self!.highScore)
        // blah blah blah...
}

So in the first line of this function I am able to call highScore and assign it to something else but inside the closure Xcode is forcing me to use "self!." and in doing so somehow unwraps an optional even though my calling it prior shows me that it is not nil. I am really confused and there are a ton of basic questions about unwrapping optionals as nil but none that I've found seem to pertain to this.

Thank you very much!

EDIT: Tried switching "weak" to "unowned" and I crash on the same line and get "Thread 3: EXC_BREAKPOINT" despite not having and breakpoints. This message is displayed in green on another screen and does not highlight that line

Upvotes: 2

Views: 544

Answers (2)

Ethan
Ethan

Reputation: 471

You can use a strong reference when trying to use self like this:

func retrieveAndAssignHighScoreFromCloudKit() {
        let high = highScore
        database.fetchRecordWithID(getRecordID(high), completionHandler: { [weak self]
            (record: CKRecord?, error: NSError?) in
            if error != nil {
                
                guard let strongSelf = self else {
                    return
                }
                print(strongSelf.highScore)
                // blah blah blah...
            }
        }
    }

Upvotes: -1

Sweeper
Sweeper

Reputation: 273987

Closures are not executed immediately.

When you pass a closure to the fetchRecordWithID method, you are telling it to execute that code when the method finishes fetching data, which can take time.

So far so good, right?

In the closure's capture list, you tell it to capture self as a weak reference. What's the purpose of this? To avoid a strong reference cycle, of course. If you don't, self holds a strong reference to the closure, and the closure holds a strong reference to self. As a result, both the closure and self will not be deinitialized.

A weak reference to self in the closure guarantees that self is deinitialized when the strong reference to the closure is broken.

I hope I've explained this well.

So now, let's say the strong reference to the closure is broken. As I said, self would be deinitialized as a result. After that, the thing finishes fetching data and it is now executing the completionHandler closure! Since self is already nil at this point, your app will crash when it reaches print(self!.highScore).

In other words, a weak reference to self implies that self can be nil. You need to unwrap it.

EDIT:

Maybe images can explain this better. (black arrows are strong references, green arrows are weak ones)

At first, it's like this:

enter image description here

When the strong reference is broken...

enter image description here

self becomes nil:

enter image description here

You can simply do something like this:

if let score = self?.highScore {
    print(score)
    // blah blah blah
} else {
    // do something else, or nothing
}

Or just this:

print(self?.highScore)

Upvotes: 0

Related Questions