Деинициализация
Деинициализатор вызывается перед освобождением экземпляра класса. Вы пишите деинициализаторы с ключевого слова deinit, аналогично как вы пишите инициализаторы с ключевого слова init. Деинициализаторы доступны только для классовых типов.
Как работает деинициализация
Swift автоматически освобождает ваши экземпляры, что освобождает память в свою очередь, когда они больше не нужны. Swift берет на себя управление памятью экземпляров через ARC(automatic reference counting), что будет объяснено в разделе Автоматический подсчет ссылок. Обычно вам не нужно вручную чистить память, когда ваши экземпляры освобождаются. Однако, когда вы работаете с вашими собственными ресурсами, вам, возможно, понадобится проводить дополнительную чистку. К примеру, если вы создаете свой класс для открытия файла и записи в него какой-то информации, а потом его закрываете, то вам понадобится закрыть файл до того как вы освободите экземпляр класса.
Определения класса могут иметь максимум один деинициализатор на один класс. Деинициализатор не принимает ни одного параметра и пишется без круглых скобок:
deinit {
// проведение деинициализации
}
Деинициализаторы вызываются автоматически прямо перед тем как освобождается экземпляр. У вас нет возможности вызывать деинициализатор самостоятельно. Деинициализаторы суперкласса наследуются их подклассами, и деинициализаторы суперкласса вызываются автоматически в конце реализации деинициализатора подкласса. Деинициализаторы суперклассов всегда вызываются, даже если подкласс не имеет своего деинициализатора.
Так как экземпляр не освобождается до тех пор пока не будет вызван деинициализатор, то деинициалиатор может получить доступ ко всем свойствам экземпляра, который он вызывает, и может изменить свое поведение, основываясь на этих свойствах(например, имя файла, который должен быть закрыт).
Деинициализаторы в действии
Здесь приведен пример деинициализатора в действии. Для создания простой игры в этом примере определяются два новых типа Bank и Player. Класс Bank управляет наличностью, которой не может быть больше 10000 монет в обороте. В игре может быть только один Bank, так что 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 может получить и меньшее количество монет, если недостаточно монет в банке.
Класс Player определяет метод win(coins:)
, который получает определенное число монет от банка и добавляет их в кошелек игрока. Класс Player так же реализует деинициализатор, который вызывается сразу после того, как экземпляр освобождается. В примере ниже деинициализатор просто возвращает все монеты игрока в банк:
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// Выведет "A new player has joined the game with 100 coins"
print("There are now \(Bank.coinsInBank) coins left in the bank")
// Выведет "There are now 9900 coins left in the bank"
Новый экземпляр Player создан со 100 монетами, если такое количество есть в банке. Экземпляр хранит опциональную переменную playerOne типа Player. Опциональный тип используется здесь, поскольку игрок может покинуть игру в любой момент. Опционал позволяет вам отслеживать присутствие игрока в игре.
Так как playerOne является опционалом, то используем восклицательный знак (!), когда мы хотим получить доступ к его свойству coinsInPurse или когда вызываем метод winCoins(_:)
:
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// Выведет "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
// Выведет "The bank now only has 7900 coins left"
Здесь наш игрок выиграл 2000 монет. Теперь у него в кошельке 2100 монет, соответственно, в банке осталось 7900 монет.
playerOne = nil
print("PlayerOne has left the game")
// Выведет "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
// Выведет "The bank now has 10000 coins"
Игрок покинул игру. Это осуществимо, если мы присвоим опционалу playerOne значение nil, что значит, что игрока больше нет. На этот момент мы больше не имеем доступа к свойствам или методам переменной playerOne, то есть у нас не стало ссылки на экземпляр класса Player, так что экземпляр освобождается и освобождается память. Прямо перед тем как это случится, автоматически вызывается деинициализатор, который возвращает монеты в банк.