Swift 🧮Представь, что у тебя есть свойство, которое не хранит значение, а вычисляет его на лету, каждый раз когда ты к нему обращаешься.
Вычисляемые свойства есть только в структурах, классах и перечислениях. Они выглядят как обычные свойства, но внутри у них код (логика)
Чем-то они очень похожи на функции, но главное отличие – вычисляемы свойства не могут принимать параметры, они могут работать только с полями внутри класса или структуры.
Допустим, у нас есть температура в цельсиях, а мы хотим получать ещё и в фаренгейтах:
struct Temperature {
var celsius: Double
// Вычисляемое свойство
var fahrenheit: Double {
return celsius * 9 / 5 + 32
}
// Аналогичная функция
func fahrenheit() -> Double {
return celsius * 9 / 5 + 32
}
}
let temp = Temperature(celsius: 25)
print(temp.celsius) // 25
print(temp.fahrenheit) // 77.0
print(temp.fahrenheit()) // 77.0
Обрати внимание: мы нигде не храним fahrenheit, он каждый раз пересчитывается из celsius
Вычисляемое свойство всегда объявляется как var и после него идёт блок кода в фигурных скобках:
var fullName: Тип {
name + " " + lastName
}
Можно (и нужно) опускать return, если код состоит из одного выражения – так лаконичнее
Самое частое использование — когда одно значение получается из других:
struct Person {
var firstName: String
var lastName: String
var fathersName: String
// Полное имя
var fullName: String {
"\(firstName) \(lastName)"
}
// Инициалы
var initials: String {
let name = firstName.first?.uppercased() ?? ""
let last = lastName.first?.uppercased() ?? ""
let father = fathersName.first?.uppercased() ?? ""
return "\(name).\(last).\(father)."
}
}
let person = Person(firstName: "Хавронья", lastName: "Усатова", fathersName: "Йосифовна")
print(person.fullName) // Хавронья Ульянова
print(person.initials) // Х.У.Й.
Мы могли добавить отлельные поля для полного имени и инициалов, но зачем ?
Ведь их мы можем вычислить из firstName и lastName и не утруждать пользователя заставляя его указывать лишнюю информацию.
Lazy Properties) 🦥Ленивые свойства — это полная противоположность вычисляемым.
Дело в том, что вычисляемые поля – пересчитывают значение всякий раз как мы к ним обращаемся, а вот ленивые – только один раз при первом обращении к ним ни раньше и не позже.
Представь, что у тебя есть свойство, которое:
Зачем тратить ресурсы на то, что, возможно, никогда не используют? Вот тут и приходят ленивые свойства.
struct PiNumber {
lazy var calculatePi: String = {
return "Очень сложные вычисления числа Пи, которые занимают огромное время"
}()
}
var piNumber = PiNumber() // Мы создали наш объект, но не вызываем calculatePi
print(piNumber.calculatePi) // 3.141592653589793238462643383279502884197169399375
Представим, что у нас есть необходимость вычислить число Пи, до тысячного знака после запятой, почему выгодно использовать именно lazy ?
Мы произведём расчёты только один раз, так как число Пи не меняется.
Если бы мы использовали вычисляемые поля – нам бы приходилось высчитывать число Пи каждый раз, как мы обращаемся к данному полю – но зачем ? Ведь число Пи – не изменится, сколько бы раз вы его не высчитывали.
Достаточно посчитать его один раз.
В этом и преимущество вычисляемого поля – оно будет посчитано лишь однажды, а затем будет возвращать уже посчитанное значение.
Но если значение может измениться при вычислении – необходимо использовать вычисляемое поле.
Хавронья могла, наконец, выйти замуж, как минимум по причине необходимости улучшить свои инициалы, и в приложении она может изменить свою фамилию и теперь она стала не “Усатова” а “Уткина”, что в прочем не сильно улучшило её инициалы.
Если бы поле fullName у нас было бы помечено как lazy – то оно бы не стало пересчитывать по новой новую фамилию, а всё так же использовала старую (ту с которой была вызвана в первый раз)
var fullName: Тип {
name + " " + lastName
}
Пользователь может изменить своё имя или фамилию, поэтому в таком случае lazy использовать будет некорректно т.к. lazy посчитает всё один раз и пересчитывать его уже не будет тем самым экономя ресурсы процессора, но ведь имя пользователя может измениться и мы станет выводить неверные данные.
В таком случае – лучше использовать вычисляемые поля, так как значение в любой момент может измениться. lazy хорош для значений, которые никогда не меняются, которые достаточно посчитать один раз.
Какое есть преимущества у ленивых свойств помимо того, что они вычислятся лишь один раз ?
Важное преимущество ленивых свойство – они вообще не начинают высчитываться пока мы их об этом не попросим (поэтому они и ленивые)
Дело в том, что когда мы инициализируем нашу структуру или класс – автоматически создаются и все поля, которые хранятся в данной структуре
struct Settings {
let userDefaults = UserDefaults()
let dateFormatter = DateFormatter()
}
Но что если таких свойств у нас много и их очень сложно создавать (например внутри DateFormatter есть ещё с десяток полей, которые тоже начнут создаваться и так по цепочке)
Обычно это не проблема – современный айфон способен создать все требуемые поля за наносекунды и пользователь никак не заметит подлагивания.
Но, давайте представим, что создание UserDefaults занимает секунду процессорного времени и получается, пользователь целую секунду будет наблюдать как создаётся класс, который ему может быть вообще и не нужен вовсе.
UserDefaults это хранилище, в котором мы можем хранить информацию, даже когда приложение закрыто.
Вполне возможно, что конкретно в текущей сессии пользователь не собирается ничего сохранять в UserDefaults в таком случае нам было бы удобно создать UserDefaults только если такая необходимость действительно понадобится.
struct Settings {
lazy var userDefaults: UserDefaults = {
print("📦 Создаём UserDefaults")
return UserDefaults.standard
}()
lazy var dateFormatter: DateFormatter = {
print("📅 Создаём DateFormatter")
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
}
// Мы создали Settings, но все ленивые поля так и не созданы.
var settings = Settings()
// Только теперь наш UserDefaults создастся – когда мы попытаемся в него, что-то сохранить
print(settings.userDefaults.set("password123", forKey: "userPass")))
Это очень удобно для тяжёлых объектов, которые не всегда нужны.
class Database {
lazy var connection = {
print("🔌 Подключаемся к БД")
return "Connection"
}()
}
let db = Database()
print("Объект создан")
print(db.connection) // Подключается только сейчас, а не в момент создания Database
Нужно быть внимательным — доступ к lazy требует mutating:
(xCode сам об этом подскажет –не тратьте ресурсы мозга на запоминание )
struct FileProcessor {
lazy var fileHandle: FileHandle? = {
print("📂 Открываем файл")
return FileHandle(forReadingAtPath: "Epstein.pdf")
}()
mutating func readData() {
guard let handle = fileHandle else { return }
// читаем данные...
}
}
var processor = FileProcessor()
processor.readData() // Здесь файл откроется
Чтение файла занимает время, пользователь может быть вообще не собирается его читать – в таком случае fileHandle выгодно сделать как lazy – ленивое поле, начнёт вычислять только, когда оно действительно понадобится (в момент его вызова)
struct Calculator {
var numbers: [Int]
/// Подсчёт среднего значения в массиве
lazy var average: Double = {
// reduce – Функция, которая складывает все значения в массиве numbers
// 0 – точка отсчёта
// + значит, что их нужно именно сложить, а не разделить (/) вычесть (-) или умножить (*)
let sum = numbers.reduce(0, +)
return Double(sum) / Double(numbers.count)
}()
/// Сложная функция сортировки
lazy var sortedNumbers: [Int] = {
numbers.sorted()
}()
}
var calc = Calculator(numbers: [5, 3, 8, 1, 9])
print(calc.average) // считает один раз. Затрачено время
print(calc.average) // берёт из кэша. Моментально
print(calc.average) // берёт из кэша. Моментально
struct ReportGenerator {
lazy var reportData: Data = {
// Чтение гигантского файла
return Data(contentsOf: URL(fileURLWithPath: "report.pdf"))
}()
lazy var charts: [Chart] = {
// Построение сложных графиков
return []
}()
}
Не факт, что пользователю вообще понадобится показывать график или данные – раз эти поля lazy они ленивы и пока их настойчиво не попросят отобразиться – не тратят усилия процессора.
| Обычные поля | Вычисляемые | Ленивые |
|---|---|---|
| Занимают память | Не занимают | Занимают после вычисления |
| Создаются сразу | Считаются каждый раз | Создаются при первом обращении |
Могут быть let |
Только var |
Только var |
Вычисляемые свойства:
fullName = name + lastName)Ленивые свойства:
“Если значение можно вычислить — сделай его вычисляемым свойством.” “Если объект тяжёлый и может не понадобиться — сделай его ленивым.”