Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I weakify "local" variables used in a block?

Tags:

ios

swift

This is not another question about [weak self]. This is about use of variables not contained by self, but rather by the wrapping function.

func someFunction(){
    someOtherFunction(completionBlock:{ [weak self] in
        self?.doStuff()
    })
}

As far as I understand, I need the [weak self] in order to prevent a retain cycle.

But what if I need to use a variable from the wrapping function, like this:

func someFunction(){
    let someVariable = MyObject()
    someOtherFunction(completionBlock:{ [weak self] in
        self?.doStuff(with: someVariable)
    })
}

This works, which makes me wonder.. How, and how long is someVariable held in memory? Can it create its own tiny retain cycle where my completion block strongly references the local someVariable? How will they be released? Should I add [weak self, weak someVariable] in the block? But then, won't someVariable be released immediately after I call someOtherFunction, because it's the end of this function - and the end of someVariable's lifetime..?

I'm having trouble completely understanding references, and can't see how my completionBlock and someVariable will ever be released.. Are blocks even released?

like image 492
Sti Avatar asked Apr 27 '18 10:04

Sti


2 Answers

Any variable referenced inside a closure will be strongly retained by that closure. You can adjust that by including a closure capture list (e.g. [weak self]), which allows you to specify the particular memory management pattern of references captured in the closure.

func someFunction(){
    let someVariable = MyObject()
    someOtherFunction(completionBlock:{ [weak self] in
        self?.doStuff(with: someVariable)
    })
}

Here, someVariable is retained by the closure, as you have stated. In this case, it has to be, because nobody else cares about it. As you've mentioned in comments, if you used a capture list of [weak someVariable] then it would always be nil when the completion block executed, as it has gone out of scope in it's original function.

A "tiny retain cycle" isn't being created. A retain cycle has to be a cycle - that is, A holds a strong reference to B, which holds a strong reference to A. someVariable doesn't have references to anything.

Once someOtherFunction has finished with the reference to the completion closure, everything goes away. The closure is just another variable as far as someOtherFunction is concerned, and it will survive as long as it is in scope.

Should I weakify “local” variables used in a block? - no, as they will then be nil by the time the block comes to use them.

like image 194
jrturton Avatar answered Sep 21 '22 15:09

jrturton


I would like to mention not so clear option, what is very usual place to create retain cycle and where you should be aware of weaken variables.

Lets consider this situation:

func someFunction() {
    let object = Something()
    object.handler = { [weak self] in 
       self?.doStuff(with: object)
    }
}

Now there is retain cycle, and object cannot be deallocated until somebody manually unset the handler. Because now object strongify itself in the captured block.

So better solution is:

func someFunction() {
    let object = Something()
    object.handler = { [weak self, unowned object] in 
       self?.doStuff(with: object)
    }
}

And good practice is pass the object as argument in handler

func someFunction() {
    let object = Something()
    object.handler = { [weak self] (object) in 
       self?.doStuff(with: object)
    }
}

So signature of this should look like:

class Something {
    var handler:((Something) -> Void)?
    deinit {
        print("retain cycle is not here")
    }
}
like image 20
Lukáš Mareda Avatar answered Sep 17 '22 15:09

Lukáš Mareda