weak и unowned
В библиотеке карточка читателя держит книгу: пока карточка выдана, книга не вернётся на полку. Но бывают записи, которые ничего не держат. В каталоге библиотеки видно, у кого сейчас книга. Когда книгу сдают, каталог сам стирает эту запись.
В прошлом уроке человек и квартира держали друг друга, и оба объекта утекали. Чтобы цикла не было, одна из двух ссылок должна быть такой записью в каталоге: знать об объекте, но не держать его.
class Person {
var home: Apartment?
deinit { print("Person freed") }
}
class Apartment {
weak var tenant: Person?
}
var anna: Person? = Person()
let flat = Apartment()
anna?.home = flat
flat.tenant = anna
anna = nil // Person freed
print(flat.tenant == nil) // trueДавайте разберём программу:
weak var tenant— квартира знает жильца, но не держит его: эта ссылка не прибавляет единицу к счётчикуPerson.- После
flat.tenant = annaу человека одна сильная ссылка —anna. anna = nilубирает её. Счётчик стал нулём, срабатываетdeinit, и программа печатаетPerson freed.- Swift сам записывает
nilвflat.tenant, поэтомуprintпечатаетtrue.
Цикла больше нет: человек держит квартиру, а квартира человека — нет.
Ссылка weak
weak-ссылка — ссылка, которая не держит объект: она не прибавляет единицу к его счётчику. Когда объект освобождается, Swift сам записывает в неё nil.
Как с ней работают:
- перед объявлением пишут слово
weak:weak var tenant: Person?; - тип у неё всегда опциональный, ведь туда должен поместиться
nil; - читают её как любой опционал: через
if let,?.или сравнение сnil.
Без ? компилятор не примет объявление:
error: 'weak' variable should have optional type 'Person?'Поэтому weak-ссылка безопасна: если объекта уже нет, вы прочитаете nil.
Какую ссылку сделать weak
Из двух ссылок цикла weak делают одну, и выбор не случаен. Спросите себя, кто кем владеет:
- сильная ссылка идёт от владельца к тому, чем он владеет: человек держит свою квартиру;
- обратная ссылка, к владельцу, делается
weak: квартира только знает жильца.
Так же устроена пара «родитель — ребёнок»: родитель держит ребёнка сильной ссылкой, а ребёнок знает родителя через weak. Если сделать наоборот, ребёнка никто не будет держать, и он освободится раньше времени.
Ссылка unowned
Бывает, что объект точно живёт дольше ссылки на него. У банковской карты всегда есть владелец, и без него карта не существует. Для такой ссылки есть второй вид — unowned-ссылка. Она тоже не держит объект, но Swift не записывает в неё nil, поэтому её обычно объявляют без ?.
class Customer {
let name: String
init(name: String) { self.name = name }
}
class Card {
unowned let owner: Customer
init(owner: Customer) { self.owner = owner }
}
var alice: Customer? = Customer(name: "Alice")
let card = Card(owner: alice!)
print(card.owner.name) // AliceРазберём, что здесь происходит:
unowned let owner: Customer— карта знает владельца, но не держит его.- Тип без
?, поэтомуcard.owner.nameчитают напрямую, безif letи?.. - Владельца держит переменная
alice, и пока она его держит, карта работает.
С unowned удобнее, но опаснее. Если владельца всё-таки освободили раньше карты, читать такую ссылку нельзя:
alice = nil
print(card.owner.name) // ✗Программа аварийно завершится: Swift сообщит, что прочитали unowned-ссылку на уже уничтоженный объект. Поэтому unowned берут, только когда уверены, что объект переживёт ссылку. Если сомневаетесь, берите weak.
Здесь картинка с каталогом заканчивается. weak похожа на запись в каталоге, которую стирают при сдаче книги. А unowned — бумажка, которую не стирает никто: если книгу уже сдали, пойти по бумажке — значит аварийно завершить программу.
На собеседовании часто спрашивают, чем weak отличается от unowned. Ответ можно строить так:
- обе ссылки не держат объект и поэтому разрывают цикл;
weak— опционал: когда объект освобождён, в нейnil, и читать её безопасно;unownedвnilне превращается, и если прочитать её после освобождения объекта, программа аварийно завершится;unownedберут, только когда объект точно живёт дольше ссылки.
Частая ошибка
Новый объект положили прямо в weak-ссылку:
let studio = Apartment()
studio.tenant = Person() // ✗
print(studio.tenant == nil) // truewarning: instance will be immediately deallocated because property 'tenant' is 'weak'Компилятор предупреждает: на новый объект нет ни одной сильной ссылки, а weak его не держит. Объект освобождается на той же строке: deinit печатает Person freed, и tenant сразу становится nil. Сначала положите объект в сильную ссылку, например let carl = Person(), а потом уже в studio.tenant.
Главное
weakиunownedне держат объект: они не прибавляют единицу к счётчику и поэтому разрывают цикл.weak-ссылка опциональная: когда объект освобождён, Swift записывает в неёnil.weakделают обратную ссылку, к владельцу: ребёнок знает родителя черезweak, а родитель держит ребёнка сильной ссылкой.unownedне становитсяnil: если прочитать её после освобождения объекта, программа аварийно завершится, поэтомуunownedберут, только когда объект живёт дольше ссылки.