emenegro
emenegro

Reputation: 6971

Memory semantics when capturing values directly in closures

I've recently read about capture lists in this article of objc.io. I think it is a great tip and I'm started to use it.

Although it is not totally clarified, I assume there are no retain cycles when capturing this way, so you get a captured strong reference but with no retain cycle to be worried about.

And I've realized that it is possible to even capture methods, not only values:

.subscribe(onNext: { [showErrorAlert, populate] result in
    switch result {
    case .success(let book):
        populate(book)
    case .error(_):
        showErrorAlert(L10n.errorExecutingOperation.localized)
    }
})

I am trying to find some documentation related to this way of capturing but I cannot find any. Is this practice safe? Is this equal to the usual dance of [weak self], strongSelf = self inside the closure?

Upvotes: 1

Views: 83

Answers (2)

Stimorol
Stimorol

Reputation: 197

  1. Capturing instance methods isn't safe unless you're sure your closure will be disposed before deinit (f.e. you absolutely sure it will trigger limited amount of times and then sequence always ends). Same for non-reactive use-cases.
  2. The method is captured strongly(and can't be captured weakly), so the closure would keep the reference, prohibiting ARC from destroying it. Hence with strongSelf behavior closure keep only weak ref to object, binds it as strong on execution start, and then releases strong reference at the execution end.

Upvotes: 1

muvaaa
muvaaa

Reputation: 600

Is this practice safe? Is this equal to the usual dance of [weak self], strongSelf = self inside the closure?

Yes and no - capturing methods of objects retains the object as well. The captured method could be accessing anything from the instance so it makes sense that it retains it.

On the other hand capturing a property does not retain the instance.

Here is a short snippet you can paste in playground to see for yourself:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class A {
    let name: String
    init(name: String) {
        self.name = name
    }

    func a() {
        print("Hello from \(name)")
    }

    func scheduleCaptured() {
        print("Scheduling captured method")
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [a] in
            a()
        }
    }

    func scheduleCapturedVariable() {
        print("Scheduling captured variable")
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [name] in
            print("Hello from \(name)")
        }
    }

    deinit {
        print("\(name) deinit")
    }
}

var a1: A? = A(name: "instance 1")
a1?.scheduleCapturedVariable()

var a2: A? = A(name: "instance 2")
a2?.scheduleCaptured()

a1 = nil
a2 = nil

The output is:

Scheduling captured variable
Scheduling captured method
instance 1 deinit
Hello from instance 1
Hello from instance 2
instance 2 deinit

You can see that instance 2 is not deinitialised until the captured block is fired, while instance 1 is deinitialised immediately after set to nil.

Upvotes: 1

Related Questions