Sobes.tech
Middle+

Is it always necessary to use weak or unowned in the Capture List?

sobes.tech AI

Answer from AI

No, not always.

Weak and unowned are used to prevent retain cycles in closures, where the closure holds a strong reference to an object, and the object in turn holds a strong reference to the closure.

Use weak or unowned when the closure captures a strong reference to self or another reference type, and there is a possibility of a retain cycle.

Situations when you don't need to use weak/unowned:

  • The closure captures value types (structures, enumerations, tuples). They are copied when captured, and retain cycles do not occur.
  • The closure captures a reference type, but there is no two-way strong reference. For example, if the closure is created locally inside a method and is not stored by the object it references.
  • The closure has a short lifetime and will execute before the object it references is deallocated. For example, a closure passed as a completion handler to a method that executes immediately.

Choosing between weak and unowned:

  • weak: Used when the captured object may become nil at any time before the closure executes. The reference is declared as an optional (ObjectType?). A nil check is required before use.
  • unowned: Used when it is known that the captured object will always exist at the time of closure execution. The reference is non-optional (ObjectType). No nil check is needed. Using unowned for an object that becomes nil will cause a crash (runtime error).
// Example where weak/unowned is not needed (capturing a struct)
struct MyStruct {
    var value: Int
}

let myStruct = MyStruct(value: 10)
let closure = {
    print(myStruct.value) // captures a copy of myStruct
}
closure()

// Example where weak/unowned is not needed (local closure)
class MyClass {
    func doSomething() {
        let localValue = 5
        let localClosure = {
            print(self.description) // captures self, but the closure is not stored by MyClass
        }
        localClosure()
    }

    var description: String {
        return "Instance of MyClass"
    }
}

let myObject = MyClass()
myObject.doSomething()

// Example where weak is needed (potentially nil)
class AsyncOperation {
    var completionHandler: (() -> Void)?

    func performOperation() {
        // Simulate asynchronous work
        DispatchQueue.global().async {
            print("Operation completed")
            self.completionHandler?() // optional chaining
        }
    }

    deinit {
        print("AsyncOperation deinitialized")
    }
}

class ViewController {
    var op: AsyncOperation?

    func setupOperation() {
        op = AsyncOperation()
        op?.completionHandler = { [weak self] in // weak self
            guard let self = self else { return }
            print("Completion handler in ViewController: \(self)")
        }
    }

    deinit {
        print("ViewController deinitialized")
    }
}

// Example where unowned is used (guaranteed to exist)
class Button {
    var action: (() -> Void)?

    func tap() {
        action?()
    }

    deinit {
        print("Button deinitialized")
    }
}

class ViewControllerWithButton {
    var button: Button

    init() {
        button = Button()
        button.action = { [unowned self] in // unowned self
            print("Button tapped in ViewControllerWithButton: \(self)")
        }
    }

    deinit {
        print("ViewControllerWithButton deinitialized")
    }
}