Sobes.tech
Middle

Какви са циклите на задържане (retain cycles) в iOS?

sobes.tech AI

Отговор от AI

Циклите на задържане възникват, когато две или повече обекта имат силни препратки един към друг, създавайки "затворен кръг". В резултат, броячът на препратките на всеки обект никога не достига нула и той не може да бъде освободен от ARC (Automatic Reference Counting). Това води до течове на памет, тъй като обектите остават в паметта дори след като вече не се използват.

Пример:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) се инициализира")
    }

    deinit {
        print("\(name) се освобождава")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) се инициализира")
    }

    deinit {
        print("Apartment \(unit) се освобождава")
    }
}

В този пример, ако Person има силна препратка към Apartment, а Apartment има силна препратка към Person, при опит за освобождаване тези обекти няма да бъдат изтрити, тъй като броячите им на препратки не достигат нула.

Как да решите проблема с цикли на задържане:

Използвайте слаби (weak) или безвластни (unowned) препратки.

  • weak: Използва се, когато е възможно свързаният обект да стане nil по време на жизнения цикъл на другия обект. Препратката става автоматично nil, когато обектът, към който сочи, бъде освободен. Това е опционално.
  • unowned: Използва се, когато е известно, че свързаният обект има същия или по-дълъг жизнен цикъл от обекта, който има препратка към него. Не може да бъде nil.

Коригиран пример с използване на weak:

Променяме препратката tenant в класа Apartment на слаба:

class Apartment {
    let unit: String
    weak var tenant: Person? // използваме weak

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) се инициализира")
    }

    deinit {
        print("Apartment \(unit) се освобождава")
    }
}

Сега, когато обектът Person бъде освободен, препратката tenant в Apartment автоматично става nil, прекъсвайки цикъла на задържане и позволявайки Apartment също да бъде освободен.

Сравнение на weak и unowned:

Характеристика weak unowned
Тип препратка Опционална Неопционална
Може ли да бъде nil? Да, когато обектът бъде освободен Не
Кога да се използва? Когато обектът може да стане nil Когато обектът не може да бъде nil
Производителност Малко по-бавна поради проверка за nil Малко по-бърза

Освен това, цикли на задържане могат да възникнат и в затваряния (closures).

Когато затваряне захваща свойства на инстанцията или self, то може да създаде силна препратка към обекта. Ако този обект също има силна препратка към затварянето (например чрез свойство-затваряне или делегат), възниква цикъл на задържане.

Как да решите проблема с цикли на задържане в затваряния:

Използвайте списък за захващане (capture list).

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // списък за захващане с weak self
            guard let self = self else { return } // проверка за `nil` за weak self
            self.value += 1
            print("Стойността сега е \(self.value)")
        }
    }
}

В този пример, [weak self] създава слаба препратка към self вътре в затварянето, предотвратявайки цикъл на задържане.