Инициализаторы — это специальные методы, которые подготавливают новый экземпляр класса или структуры к использованию.
Они гарантируют, что все свойства имеют начальные значения перед первым использованием объекта.
class Person {
var name: String
var age: Int
// Простой инициализатор
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "Виктория", age: 19)
В данном инициализаторе нет никакой дополнительной логики, он лишь запрашивает значения для полей name и age
class Car {
var brand = "Unknown"
var year = 2024
// Инициализатор не нужен так как нет полей с незаданными значениями
}
let car = Car() // brand = "Unknown", year = 2024
В данном случае у нас нет полей, для которых нам требуются данные, по той причине, что мы уже указали значения по умолчанию.
В таком случае init нам не требуется, но вместе с тем – никто не запрещает нам его добавить!
class Rectangle {
var width: Double
var height: Double
/// Если есть значение по умолчанию – данное значение можно не указывать
init(width: Double = 1.0, height: Double = 1.0) {
self.width = width
self.height = height
}
}
let square = Rectangle(width: 5.0, height: 5.0)
let defaultRect = Rectangle() // width = 1.0, height = 1.0
Данный инициализатор имеет немного дополнительной логики – мы указываем, что ширина и высота будут равны 1.0 если не указать иного.
Как вы могли бы заметить – square мы проинициализировали как (width: 5.0, height: 5.0), но для defaultRect мы не стали указывать никакого значения в инициализаторе и у нас они равны 1.0 (так как в init мы об этом указали)
Удобным (convenience) он называется по той причине, что у нас всегда есть основной init(brand: String, year: Int) , который может быть довольно сложен, а мы можем добавить инициализатор попроще, но он всегда должен вызывать внутри себя основной!
class Vehicle {
var brand: String
var year: Int
init(brand: String, year: Int) {
self.brand = brand
self.year = year
}
// удобный инициализатор
convenience init(brand: String) {
self.init(brand: brand, year: 2024)
}
convenience init() {
self.init(brand: "Unknown")
}
}
let vehicle1 = Vehicle(brand: "Toyota", year: 2020)
let vehicle2 = Vehicle(brand: "Honda") // year = 2024
let vehicle3 = Vehicle() // brand = "Unknown", year = 2024
Обратите внимание – упрощённый инициализатор convenience init() внутри себя вызывает основной self.init(brand: "Unknown") и указывает значение бренда как "Unknown"
Получается, если пользователь не знает что указать в поле бренд, но хочет создать Vehicle он может воспользоваться упрощённым инициализаторром, который не спрашивает о бренде.
Инициализаторы, которые могут вернуть nil при неудачной инициализации.
class Person {
var name: String
var age: Int
init?(name: String, age: Int) {
guard age >= 0 else { return nil }
guard !name.isEmpty else { return nil }
self.name = name
self.age = age
}
}
if let person = Person(name: "Анна", age: 25) {
print("Person создан: \(person.name)")
}
let invalidPerson = Person(name: "", age: 25) // nil
let invalidAge = Person(name: "Анна", age: -5) // nil
Обратите внимание – init? означает, что у нас может и не получится создать объект, например если указан отрицательный возраст – то в init мы вернём nil и объект не создастся.
Структуры автоматически получают инициализатор.
struct Point {
var x: Double
var y: Double
}
let point = Point(x: 10.0, y: 20.0) // автоматический инициализатор (Мы не видим его внутри структуры, но он есть)
Это было бы полностью идентично, если бы мы написали слеудющее:
struct Point {
var x: Double
var y: Double
// Данный идентификатор уже есть в Структуре по умолчанию
init(x: Double, y: Double) {
self.x = x
self.y = y
}
}
До сих пор мы рассматривали примеры, в который практически отсутствовала логика в инициализаторе – мы лишь прямолинейно указывали значение для всех требуемых полей и вы, верно задаётесь вопросом
и это всё ?
Нет, инициализаторы способны на многое – их задача состоит из следующих пунктов:
struct Size {
var width: Double
var height: Double
/// Специальный инициализатор для квадрата
init(side: Double) {
self.width = side
self.height = side
/// Запускаем функцию, в ту же секунду как объект проинициализировался
calculateSquare()
}
func calculateSquare() {
print("Высчитываем площадь квадрата: \(width * height)")
}
}
let square = Size(side: 10.0) // "Высчитываем площадь квадрата: 100"
Обратите внимание – в данном случае мы добавили инициализатор для квадрата, которому очевидно не нужно указывать все стороны.
Это пример инициализатора, который упрощает создание именно квадрата – нам достаточно указать лишь одну из сторон, в сам инициализатор уже позаботится о том, чтобы каждое из полей имело значение self.width = side и self.height = side
Деинициализаторы вызываются перед освобождением экземпляра класса.
Это прямая противоположность инициализатору. Если инициализатор это логика в момент создания), то деинициализатор это логика в момент уничтожения объекта класса.
Используется не часто, но нужно понимать логику.
class FileHandler {
var filename: String
/// Знакомый уже нам инициализатор
init(filename: String) {
self.filename = filename
print("Файл \(filename) открыт")
}
/// Как только объект класса будет уничтожен – автоматически вызовется
deinit {
print("Файл \(filename) закрыт")
// очистка ресурсов, закрытие файлов и т.д.
}
}
var handler: FileHandler? = FileHandler(filename: "data.txt")
/// Как только мы скажем, что объект класса – nil, он будет уничтожен и последнее, что он сделает – вызовев deinit
handler = nil // "Файл data.txt закрыт"
class BankAccount {
let accountNumber: String
var balance: Double
/// Инициализатор
init(accountNumber: String, initialBalance: Double) {
self.accountNumber = accountNumber
self.balance = initialBalance
print("Счет \(accountNumber) создан. Баланс: \(balance)")
}
/// Деинициализатор
deinit {
print("Счет \(accountNumber) закрыт. Финальный баланс: \(balance)")
// отправка финального отчета, закрытие соединений
}
}
func testAccount() {
let account = BankAccount(accountNumber: "12345", initialBalance: 1000)
account.balance -= 500
// при выходе из функции счет будет закрыт
}
testAccount()
// Счет 12345 создан. Баланс: 1000
// Счет 12345 закрыт. Финальный баланс: 500
Обратите внимание: так как мы создали BankAccount внутри функции – система понимает, что как только функция отработает его нужно уничтожить так как в нём уже нет смысла.
Дело в том, что всякий раз, когда мы создаём объект (let account = BankAccount(accountNumber: "12345", initialBalance: 1000)) он занимает место в оперативной памяти телефона.
Нам же нужно хранить данные всех полей ?
У нас тысячи функций, десятки тысяч созданных объектов, если бы они не самоуничтожались, как только в них теряется смысл – у нас бы закончилось всё доступное место.
И как только реальный объект будет уничтожен, на последнем издыхании он скажет deinit.........
Инициализаторы в Swift обеспечивают безопасное создание объектов с гарантией, что все свойства имеют корректные начальные значения.