Skip to content

Latest commit

 

History

History
144 lines (104 loc) · 5.34 KB

File metadata and controls

144 lines (104 loc) · 5.34 KB

Retain cycle

  1. Automatic Reference Counting

Retain cycle (circular reference) возникает, когда два или более объекта ссылаются друг на друга, не позволяя их деаллоцировать. В результате могут возникать утечки памяти, что приводит к тому, что ваше приложение потребляет больше памяти, чем необходимо.

Друг на друга ссылаются

Open

Наглядная иллюстрация приведенного ниже кода:

Имплементация retain cycle, когда объекты ссылаются друг на друга:

final class Person {
    let firstName: String
    let lastName: String
    var parent: Person?
    var kid: Person?
    
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    deinit {
        print("Person \(firstName) deinit called")
    }
}

var kidJohn: Person? = Person(firstName: "John", lastName: "Deere")
var parentSara: Person? = Person(firstName: "Sara", lastName: "Deere")

kidJohn?.parent = parentSara
parentSara?.kid = kidJohn

Установим экземпляры класса kidJohn и parentSara nil:

// deinit не вызовется
kidJohn = nil
parentSara = nil

Экземпляры (kidJohn и parentSara) не будут освобождены, нет сообщений «deinit», произойдет утечка памяти.

Почему это происходит? Для этого рассмотрим визуальный пример:

Решение проблемы

Использование так называемых weak (слабых) ссылок — это способ избежать retain cycle. Если вы объявляете ссылку слабой (weak)), то эта ссылка не препятствует освобождению экземпляра. Давайте изменим наш код и посмотрим, что произойдет:

weak var parent: Person?
weak var kid: Person?

Остаются только слабые ссылки, а экземпляры будут удалены.

Ссылка на самого себя

Open

Если мы напишем следующий код:

final class Person {
    let firstName: String
    let lastName: String
    
    // deinit вызовется
    // тк fullNameVarFromClosure это не closure, это вычисляемое свойство
    // которое возвращает String
    // оно по дефолту @noescape и никаких capture list здесь не нужно
    // [ссылка](https://michael-kiley.medium.com/why-your-lazy-vars-arent-creating-strong-reference-cycles-in-ios-d512ff2c9403)
    lazy var fullNameVarFromClosure : String = {
        return self.firstName + " " + self.lastName
    }()
    // deinit вызовется
    var fullNameComputedVar: String {
        self.firstName + " " + self.lastName
    }
    // deinit не вызовется
    lazy var fullNameClosure: () -> String = {
        return self.firstName + " " + self.lastName
    }

    // deinit не вызовется
    lazy var closure: () -> Void = { [self] in
        self.closure()
    }
    
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    deinit {
        print("Person \(firstName) deinit called")
    }
}

var personJohn: Person? = Person(firstName: "John", lastName: "Deere")
// deinit вызовется
print(personJohn!.fullNameVarFromClosure)
personJohn = nil
// deinit не вызовется
print(personJohn!.fullNameClosure)
personJohn = nil

Несмотря на то, что внутри fullNameVar мы ссылаем на самого себя deinit будет вызван! Причина в том что fullNameVar это value тип.

А fullNameClosure - это closure, то есть reference тип.

Решение проблемы

lazy var fullNameClosure: () -> String = { [weak self] in
    return self!.firstName + " " + self!.lastName
}


3.1.3.1.3 Autoreleasepool Theme | Back To iOSWiki Contents | 3.1.3.1.4.2 Strong Reference Theme