Sobes.tech
Middle

Jak można zapobiec powstaniu cyklu zatrzymania (retain cycle) w iOS?

sobes.tech AI

Odpowiedź od AI

Cyklów utrzymania (retencji) można zapobiec, stosując poprawne użycie słabych (weak) lub nieutrzymujących (unowned) referencji.

Słabe referencje (weak):

  • Nie zwiększają licznika referencji obiektu.
  • Mogą stać się nil (są nullable).
  • Używa się ich, gdy obiekt może istnieć niezależnie od innego (np. delegat).
class DelegateExample {
    weak var delegate: SomeDelegateType? // Słaba referencja do delegata
}

Nieutrzymujące referencje incondycyjne (unowned):

  • Nie zwiększają licznika referencji obiektu.
  • Są gwarantowanie niepuste (non-nil).
  • Używa się ich, gdy jeden obiekt wyraźnie posiada inny, ale obiekt zależny może istnieć tylko razem z właścicielem (np. zamknięcia, które przechwytują self w klasie, gdzie zamknięcie "należy" wyraźnie do instancji klasy). Dostęp do unowned spowoduje błąd w czasie wykonywania, jeśli obiekt został już zwolniony.
class Owner {
    var closure: (() -> Void)?

    func setupClosure() {
        closure = { [unowned self] in // Nieutrzymująca referencja incondycyjna do self
            // Użycie self
            print("Używanie unowned self")
        }
    }
}

Użycie w zamknięciach: Zamknięcia mogą tworzyć cykle utrzymania, jeśli przechwytują instancje klas przez silne referencje, a ta instancja zawiera również silną referencję do zamknięcia. Aby tego uniknąć, używa się list przechwytywania (capture lists) na początku zamknięcia:

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Słaba referencja do self
        guard let strongSelf = self else {
            return "" // Obsługa przypadku, gdy self jest już nil
        }
        if let text = strongSelf.text {
            return "<\(strongSelf.name)>\(text)</\(strongSelf.name)>"
        } else {
            return "<\(strongSelf.name)/>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) jest wywoływany")
    }
}

Wybór między weak a unowned zależy od semantyki relacji między obiektami:

  • weak: gdy referencja może stać się nil w trakcie cyklu życia obiektu, na który się odwołuje.
  • unowned: gdy obiekt, na który się odwołuje, istnieje gwarantowanie dłużej lub tak długo jak obiekt, który go odwołuje, i referencja nigdy nie stanie się nil.