Sobes.tech
Middle+

Czy zawsze trzeba używać weak lub unowned w liście przechwytywania?

sobes.tech AI

Odpowiedź od AI

Nie, nie zawsze.

Weak i unowned są używane do zapobiegania cyklom silnych referencji (retention cycles) w zamknięciach, gdy zamknięcie utrzymuje silną referencję do obiektu, a obiekt z kolei utrzymuje silną referencję do zamknięcia.

Konieczne jest użycie weak lub unowned, gdy zamknięcie przechwytuje silną referencję do self lub innego typu referencyjnego, i istnieje ryzyko powstania cyklu silnych referencji.

Sytuacje, gdy nie trzeba używać weak/unowned:

  • Zamknięcie przechwytuje typy wartości (struktury, wyliczenia, krotki). Są one kopiowane podczas przechwytywania, i nie powstają cykle silnych referencji.
  • Zamknięcie przechwytuje typ referencyjny, ale nie istnieje dwukierunkowy cykl silnych referencji. Na przykład, jeśli zamknięcie jest tworzone lokalnie w metodzie i nie jest przechowywane w obiekcie, na który się odwołuje.
  • Zamknięcie ma krótkie życie i zostanie wykonane zanim obiekt, na który się odwołuje, zostanie zwolniony. Na przykład, zamknięcie przekazane jako obsługa zakończenia, które jest wykonywane natychmiast.

Wybór między weak a unowned:

  • weak: używane, gdy przechwycony obiekt może stać się nil w dowolnym momencie przed wykonaniem zamknięcia. Referencja jest opcjonalna (ObjectType?). Wymaga sprawdzenia na nil przed użyciem.
  • unowned: używane, gdy wiadomo, że przechwycony obiekt zawsze będzie istniał w momencie wykonania zamknięcia. Referencja nie jest opcjonalna (ObjectType). Nie wymaga sprawdzania na nil. Użycie unowned dla obiektu, który stał się nil, spowoduje błąd w czasie działania.
// Przykład, gdy nie jest potrzebne `weak`/`unowned` (przechwytywanie struktury)
struct MyStruct {
    var value: Int
}

let myStruct = MyStruct(value: 10)
let closure = {
    print(myStruct.value) // Kopiuje myStruct
}
closure()

// Przykład, gdy nie jest potrzebne `weak`/`unowned` (lokalne zamknięcie)
class MyClass {
    func doSomething() {
        let localValue = 5
        let localClosure = {
            print(self.description) // Przechwytuje self, ale nie jest przechowywane w MyClass
        }
        localClosure()
    }

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

let myObject = MyClass()
myObject.doSomething()

// Przykład, gdy potrzebne jest `weak` (potencjalnie nil)
class AsyncOperation {
    var completionHandler: (() -> Void)?

    func performOperation() {
        // Symulacja pracy asynchronicznej
        DispatchQueue.global().async {
            print("Operation completed")
            self.completionHandler?() // łańcuch opcjonalny
        }
    }

    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")
    }
}

// Przykład, gdy potrzebne jest `unowned` (gwarantowane istnienie)
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")
    }
}