Деініціалізація
Деініціалізатори викликаються безпосередньо перед деалокацією екземплярів. Деалокатори оголошуються за допомогою ключового слова deinit
, аналогічно до ініціалізаторів, котрі оголошуються за допомогою ключового слова init
. Деініціалізатори доступні лише для класів.
Як працює деініціалізація
Swift автоматично деалокує екземпляри, котрі більше не потрібні, щоб вивільнити ресурси. Swift керує пам’яттю екземплярів за допомогою автоматичного підрахунку посилань (automatic reference counting, або ARC), що детально описано в розділі Автоматичний підрахунок посилань. Як правило, при деалокації екземплярів не потрібно робити якогось ручного прибирання. Однак, при роботі з деякими ресурсами, іноді потрібно вивільняти їх наприкінці життя екземпляру. Наприклад, якщо якийсь клас відкриває файл та записує до нього дані, може знадобитись закрити цей файл перед деалокацією екземпляра цього класу.
Оголошення класу може мати не більше одного деініціалізатора. Деініціалізатор не приймає жодних параметрів, та записується без круглих дужок:
deinit {
// виконання деініціалізації
}
Деініціалізатори викликаються автоматично, одразу перед тим як відбудеться деалокація екземпляру. Викликати ініціалізатори явно заборонено. Деініціалізатор батьківського класу успадковується його нащадками, при цьому деініціалізатор батьківського класу викликається автоматично наприкінці реалізації деініціалізатора класу-нащадка. Деініціалізатор батьківського класу викликається завжди, навіть якщо у класі-нащадку немає власного деініціалізатора.
Оскільки екземпляр не деалокується до завершення виклику деініціалізатора, деініціалізатор має доступ до всіх властивостей екземпляру, на якому він викликається, та може змінювати власну поведінку базуючись на цих властивостях (наприклад, визначити ім’я файлу, котрий потрібно закрити).
Деініціалізатори в дії
Ось приклад деініціалізатора в дії. В даному прикладі є два нових типи, Bank
та Player
(що моделюють банк та гравця відповідно) для простої гри. Клас Bank
керує ігровою валютою, котра має в обігу не більше 10 000 монет. У грі може бути не більше одного банку, тому Bank
реалізовано як клас із властивостями та методами типу для зберігання його поточного стану та керування ним:
class Bank {
static var coinsInBank = 10_000
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}
Клас Bank
відслідковує поточну кількість монет, які в ньому тримаються, у властивості coinsInBank
. Він також має два методи: distribute(coins:)
та receive(coins:)
– для видачі монет гравцю та повернення їх назад у банк відповідно.
Метод distribute(coins:)
перевіряє, що банк має достатньо монет перед їх видачею. Якщо в банку недостатньо монет, Bank
повертає менше монет, ніж запитувалось (зокрема, повертає нуль, якщо в банку закінчились монети). Він повертає цілочисельне значення, що є фактичною кількістю виданих монет.
Метод receive(coins:)
просто додає кількість монет, що повертаються назад, до загальної кількості монет у банку.
Клас Player
описує гравця у грі. Кожен гравець має певну кількість монет, що зберігаються в його гаманці в будь-який час. Це представлено властивістю гравця coinsInPurse
:
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
deinit {
Bank.receive(coins: coinsInPurse)
}
}
Кожен екземпляр Player
ініціалізується стартовим капіталом з визначеної кількості монет з банку, при цьому гравцю може бути видано менше монет, ніж вказано, якщо їх не вистачає в банку.
У класі Player
визначено метод win(coins:)
, котрий бере з банку певну кількість монет та додає їх до гаманця гравця. У класі Player
також реалізовано деініціалізатор, котрий викликається в момент перед деалокацією екземпляру класу Player
. В даному випадку, деініціалізатор просто повертає всі монети гравця назад у банк:
var playerOne: Player? = Player(coins: 100)
print("До гри приєднався новий гравець з \(playerOne!.coinsInPurse) монетами")
// Надрукує "До гри приєднався новий гравець з 100 монетами"
print("У банку лишилось \(Bank.coinsInBank) монет")
// Надрукує "У банку лишилось 9900 монет"
Вище створено новий екземпляр класу Player
, із запитом 100 монет, якщо вони доступні. Цей екземпляр Player
зберігається в опціональній змінній типу Player
на ім’я playerOne
. Опціональне значення використовується тут тому що гравці можуть вийти з гри у будь-який момент. Опціональне значення дозволяє відслідковувати, чи є у даний момент гравець у грі.
Оскільки змінна playerOne
є опціональною, звернення до її властивості coinsInPurse
та методу winCoins(_:)
відбуваються через знак оклику (!
):
playerOne!.win(coins: 2_000)
print("Гравець PlayerOne виграв 2000 монет і тепер має \(playerOne!.coinsInPurse) монет")
// Надрукує "Гравець PlayerOne виграв 2000 монет і тепер має 2100 монет"
print("У банку тепер лишилось \(Bank.coinsInBank) монет")
// Надрукує "У банку тепер лишилось 7900 монет"
Тут гравець виграв 2000 монет. Тепер у нього в гаманці міститься 2100 монет, а у банку залишилось лише 7900 монет.
playerOne = nil
print("Гравець PlayerOne покинув гру")
// Надрукує "Гравець PlayerOne покинув гру"
print("Банк тепер має \(Bank.coinsInBank) монет")
// Надрукує "Банк тепер має 10000 монет"
Гравець тепер покинув гру. Це моделюється присвоєнням опціональній змінній playerOne
значення nil
, що значить “немає екземпляру Player
.” В момент, коли сталось присвоєння, посилання змінної playerOne
на екземпляр Player
розірвалось. Оскільки більше немає властивостей чи змінних, котрі посилаються на цей же екземпляр Player
, він деалокується для вивільнення пам’яті. В момент перед деалокацією автоматично викликається його деініціалізатор, і монети повертаються назад у банк.