Lazy var swift что это
Свойства предназначены для хранения состояния объекта. Свойства бывают двух типов:
- Хранимые свойства (stored properties) — переменные или константы, определенные на уровне класса или структуры
- Вычисляемые свойства (computed properties) — конструкции, динамически вычисляющие значения. Могут применяться в классе, перечислении или структуре
Хранимые свойства
Хранимые свойства представляют простейшую форму хранения значений в виде констант или переменных:
class User
При определении хранимых свойств следует предоставить им значения по умолчанию либо напрямую, либо в одном из инициализаторов класса:
class User < var age: Int let name: String init()< name = "Tom" age = 22 >>
После определения свойств класса мы можем получить к ним доступ:
var user: User = User() print(user.age) // 22 print(user.name) // Tom
Ленивые хранимые свойства
Ленивые хранимые свойства (lazy stored properties) представляют такие свойства, значение которых устанавливается при первом обращении к ним. Использование подобных свойств позволяет более эффективно использовать память, не загромождая ее ненужными объектами, которые могут не потребоваться.
Ленивые свойства определяются с помощью ключевого слова lazy :
class User
Модификатор lazy может использоваться только для свойств, которые определяются с помощью var.
Вычисляемые свойства
Вычисляемые свойства (computed properties) не хранят значения, а динамически вычисляют его, используя блок get (getter). Также они могут содержать вспомогательные блок set (setter), который может применяться для установки значения.
Общий синтаксис определения вычисляемого свойства следующий:
var имя_свойства: тип < get < //вычисление значения >set (параметр) < // установка значения >>
Блок get или геттер срабатывает при получении значения свойства. Для возвращения значения должен использоваться оператор return.
Блок set или сеттер срабатывает при установке нового значения. При этом в качестве параметра в блок передается устанавливаемое значение.
Рассмотрим следующий пример. Допустим, у нас есть программа, которая вычисляет прибыль при вложеннии определенной суммы на определенный период:
class Account < var capital: Double = 0 // сумма вклада var rate: Double = 0.01 // процентная ставки var profit: Double< get< return capital + capital * rate >set(newProfit) < self.capital = newProfit / (1 + rate) >> init(capital: Double, rate: Double) < self.capital = capital self.rate = rate >> var myAcc: Account = Account (capital: 1000, rate: 0.1) print (myAcc.profit) // 1100 // ожидаемая прибыль myAcc.profit = 1210 print(myAcc.capital) // 1100 - необходимая сумма вклада для получения этой прибыли
Свойство profit представляет вычисляемое свойство. Его блок get возвращает результат арифметических операций:
В данном случае этот блок срабатывает, когда мы обращаемся к свойству profit:
print (myAcc.profit)
Блок set позволяет реализовать обратную связь между суммой прибыли и суммой вклада: мы вводим ожидаемую прибыль и получим сумму вклада, необходимую для получения этой прибыли:
set(newProfit) < self.capital = newProfit / (1 + rate) >
Этот блок срабатывает при установке значения:
myAcc.profit = 1210
Параметр newProfit в блоке set это и есть присваиваемое значение 1210. newProfit — это случайное название параметра, которое может быть любым, важно понимать, что оно передает значение типа, которое представляет свойство — типа Double.
Также мы можем использовать сокращенную форму блока set:
Переданное значение теперь передается через ключевое слово newValue .
Вычисляемые свойства только для чтения
Не всегда в вычисляемых свойствах необходим блок set. Иногда нам не нужно устанавливать новое значение свойства, а требуется только возвратить его. В этом случае мы можем опустить блок set и создать свойство только для чтения (read-only computed property):
class Account < var capital: Double = 0 // сумма вклада var rate: Double = 0.01 // процентная ставки var profit: Double< return capital + capital * rate >init(capital: Double, rate: Double) < self.capital = capital self.rate = rate >> var myAcc: Account = Account (capital: 1000, rate: 0.1) print (myAcc.profit) // 1100
Наблюдатели свойств
Наблюдатели свойств (property observers) следят за изменением значений свойств и при необходимости могут реагировать на эти изменения. Обозреватели свойств вызываются каждый раз при установке нового значения свойства, даже если новое значение не отличается от старого.
Наблюдатели свойств могут быть двух типов:
- willSet : вызывается перед установкой нового значения
- didSet : вызывается после установки нового значения
Общий синтаксис наблюдателей свойств можно выразить следующим образом:
var свойство: тип < willSet (параметр)< // выражения >didSet (параметр) < // выражения >>
Применим наблюдатели свойств:
class Account < var capital: Double < willSet (newCapital)< print("Старая сумма вклада: \(self.capital) Новая сумма: \(newCapital)") >didSet (oldCapital) < print("Сумма вклада увеличена на \(self.capital - oldCapital)") >> var rate: Double init(capital: Double, rate: Double) < self.capital = capital self.rate = rate >> var myAcc: Account = Account(capital: 1000, rate: 0.1) myAcc.capital = 1200 // вывод консоли // "Старая сумма вклада: 1000 Новая сумма: 1200" // "Сумма вклада увеличена на 200"
Интервью — 10 вопросов про Swift. Часть 1
Салют, хабровчане! Майские закончились, и мы продолжаем запускать новые курсы. Эта статья приурочена к старту курса «iOS-разработчик». Обучение начнется 28 мая, а до тех пор у вас есть возможность побольше узнать о языке Swift и его особенностях. Это первая статья из трех, где в формате интервью рассматриваются достоинства и особенности языка Swift, на котором мы учим создавать приложения под iOS на наших курсах.
Что такое Swift и чем он хорош?
Swift — это универсальный и мультипарадигмальный язык программирования, разработанный корпорацией Apple для разработки решений под iOS, macOS, watchOS, tvOS и даже Linux.
- Удобочитаемость — у языка Swift очень чистый и простой синтаксис, который легко как пишется, так и читается.
- Легкая поддержка — на выходе получается гораздо меньше кода и уровней наследования, а весь проект превращается в один swift-файл.
- Безопасная платформа — компилируйте и фиксите ошибки прямо во время написания кода.
- Высокая скорость — невероятно быстрый и производительный компилятор LLVM преобразует написанный на Swift код в нативный, чтобы получить максимум отдачи от устройств. Сам синтаксис и стандартная библиотека также оптимизированы для быстрой работы.
- Поддержка динамических библиотек.
- Открытый исходный код.
В чем разница между классом и структурой?
В Swift есть четыре основных различия между классом и структурой. Классы имеют некоторые возможности, которых нет у структур:
- Приведение типов — позволяет проверять и интерпретировать классы во время выполнения.
- Подсчет ссылок — позволяет использовать более одной ссылки для каждого экземпляра класса.
- Наследование — позволяет одному классу наследовать характеристики другого.
- Деинициализаторы — позволяют каждому экземпляру класса освобождать все назначенные ему ресурсы.
В каких случаях использовать класс, а в каких — структуру?
В качестве простой шпаргалки: структуры стоит использовать тогда, когда выполняется одно или несколько из следующих условий.
- Задача структуры в том, чтобы инкапсулировать несколько относительно простых значений данных;
- Можно ожидать, что инкапсулированные значения будут копироваться, а не ссылаться;
- Хранящиеся в структуре свойства сами являются типами значений, которые также копируются, а не ссылаются;
- Структура не должна наследовать свойства и поведение другого существующего типа.
Как передавать переменные в качестве ссылок?
Переменную можно передавать в качестве ссылки с помощью параметра inout. Inout означает, что изменение локальной переменной также изменит передаваемые параметры.
var value: String = “Apple” func changeString(newValue:inout String) < newValue = “Samsung” print(newValue) // Output:Samsung print(value) // Output:Samsung >changeString(newValue:&value)
Что такое модуль?
- Модуль — это отдельная единица в распределении кода;
- Платформа или приложение, которое создается и распространяется как отдельная единица и может быть импортировано другим модулем;
- Каждая цель сборки — пакет приложений или фреймворк в Xcode рассматривается как отдельный модуль.
Чем отличаются уровни доступа в Swift?
В Swift есть пять различных уровней доступа для сущностей в коде:
- Открытый доступ — классы с открытым доступом могут являться подклассами или быть переопределены подклассами в модуле, где они определены, или в любом другом, который импортирует модуль, где они определены;
- Публичный доступ — классы с публичным доступом могут являться подклассами или быть переопределены подклассами только в рамках модуля, где они определены;
- Внутренний доступ — сущности могут использоваться в любом исходном файле из определяющего модуля, но не в исходном файле вне этого модуля;
- Файл-частный доступ — использование сущностей ограничено собственным определяющим исходным файлом;
- Частный доступ — использование сущностей ограничено вложенным объявлением и расширениями этого объявления, которые находятся в одном файле.
Что такое отложенная инициализация?
Отложенная инициализация — техника задержки создания объекта или выполнения другого процесса до тех пор, пока этот процесс не станет необходимым. Задержку можно использовать только с классами и структурами. Однако стоит понимать, что свойство lazy не является безопасным, потому что не инициализируется автоматически.
Вам всегда нужно объявлять свойство lazy как переменную с использованием var. Свойства констант всегда должны иметь значение до завершения инициализации, поэтому они не могут быть отложенными.
Что такое кортеж?
Кортеж — это группа из нуля или более значений, представленных как одно значение. Они обычно используются для возврата нескольких значений из функции вызова. В Swift два вида кортежей.
Именной кортеж
let nameAndAge = (name:”Midhun”, age:7) Access the values like: nameAndAge.name //Output: Midhun nameAndAge.age //Output: 7
Безымянный кортеж
В этом типе кортежа мы не указываем имен для элементов.
let nameAndAge = (“Midhun”, 7) Access the values like: nameAndAge.0 //Output: Midhun nameAndAge.1 //Output: 7
Что такое перечисления?
С помощью перечисления определяется общий тип для группы связанных значений и обеспечивается возможность безопасной работы с этими значениями в коде. В отличие от C и Objective-C, в Swift перечислениям при создании не присваиваются целочисленные значения по умолчанию.
Что такое связанные значения?
Связанные значения очень похожи на переменные, связанные с одним из случаев перечисления.
enum Barcode
Определяем тип перечисления Barcode, который может принимать либо значение upc со связанным значением типа (Int, Int, Int, Int), либо значение qrCode со связанными значением типа String.
Иногда возможность хранить связанные значения других типов рядом со значениями case может быть полезна. Это позволяет хранить дополнительную пользовательскую информацию вместе со значением case и позволяет изменять эту информацию при каждом использовании этого case в коде.
Конец первой части.
Вторая часть.
Приглашаем всех желающих поучаствовать в бесплатном вебинаре, на котором мы расскажем, чему научит вас этот курс.
- ios разработка
- swift
- мобильная разработка
- языки программирования
- Блог компании OTUS
- Программирование
- Разработка под iOS
- Разработка мобильных приложений
- Swift
Lazy var swift что это
Наконец-то добрался до относительно новой возможности языка Swift. И сейчас мы сначала разберемся на практике как можно написать свой собственный модификатор lazy с помощью @propertyWrapper. А затем напишем действительно полезный модификатор для поддержки темной темы.
Немного о @propertyWrapper
Дословно это обертка над свойством.
Свойство это пара методов: get и set. Первый отвечает за чтение, второй за запись. Опционально свойство может иметь переменную для хранения значения. Также метода записи может и не быть, тогда свойство можно только читать.
Модификатор @propertyWrapper позволяет обернуть свойство, и таким образом добавить логику выполнения его методов get и set. А что это будет за логика решать вам.
Что такое lazy?
Это модификатор, который позволяет создать значение свойства лениво, только в момент обращения к свойству. Другими словами, мы откладываем создание на тот момент, когда свойство действительно нужно.
Но зачем нам это?
- Если есть вероятность, что свойство может быть не использовано. Тогда нет смысла создавать его значение сразу;
- Если создание значения свойства занимает длительное время, то его лучше отложить. Тогда сам объект, где объявлено свойство, будет создан быстро;
- Значение свойства не может быть создано в момент создания объекта, где это свойство объявлено. К примеру, нужно дождаться готовности одной из зависимостей объекта;
- Ваш вариант.
Более подробно вы можете почитать в официальной документации языка Swift или в одной из множества статей на эту тему. На этом закончим со скучной непонятной теорией и перейдем к практике.
Подготовка
Для начала подготовим пример. Пусть у нас есть объект, который может предоставить помощь пользователю. Но возможно пользователь разберется во всем сам, и тогда нет необходимости создавать этот объект сразу.
final class Game lazy var help: HelpProvider = print("Taking Fire, Need Assistance") return HelpProvider() >()> let game = Game() // Output:game.help // Output: Taking Fire, Need Assistancegame.help // Output:
Отлично, у нас есть ленивое свойство help и его значение будет создано только в момент обращения к нему. Затем оно переиспользуется без пересоздания.
То же самое, но без lazy
Теперь откажемся от стандартного модификатора lazy и напишем то же самое без него. Для этого нам потребуется ещё одна переменная для хранения значения свойства. А также придется написать всю ленивую логику.
final class Game var help: HelpProvider // Сначала проверяем, может значение уже создано if let storage = helpStorage < return storage > // Иначе создаем и сохраняем в отдельной переменной print("Taking Fire, Need Assistance") let newStorage = HelpProvider() helpStorage = newStorage return newStorage > private var helpStorage: HelpProvider?> let game = Game() // Output:game.help // Output: Taking Fire, Need Assistancegame.help // Output:
Всё, теперь осталось завернуть это в @propertyWrapper.
Заворачиваем в @Lazy
Для начала напишем самую простую версию. Затем оценим её минусы и попробуем улучшить. wrappedValue является обязательным и его тип должен совпадать с типом переменной, к которой применяется @propertyWrapper.
@propertyWrapper final class Lazy var wrappedValue: HelpProvider if let existStorage = storage < return existStorage > print("Taking Fire, Need Assistance") let newStorage = HelpProvider() self.storage = newStorage return newStorage > private var storage: HelpProvider?>
Использование очень напоминает применение модификатора lazy. Правда создание значения свойства скрыто в @propertyWrapper.
final class Game @Lazy var help: HelpProvider> let game = Game() // Output:game.help // Output: Taking Fire, Need Assistancegame.help // Output:
Какие минусы вы заметили? На самом деле пока это решение выглядит как один сплошной минус.
- Можно использовать только для HelpProvider — и это ужасно;
- Создание объекта не может быть изменено без изменения реализации @propertyWrapper.
Чуть ниже я привожу более сложную реализацию. Если вы начинающий, то будьте осторожны. Это может вас шокировать.
Убираем минусы
Чтобы избавится от первого минуса, воспользуемся преимуществом Generics. Объявим @propertyWrapper как LazyOnce , где T это тип, который будет подставляться на этапе компиляции в зависимости от контекста. Таким образом можно будет использовать любой тип.
Со вторым минусом всё сложнее. Нам нужно передать closure, в котором будет скрыто создание объекта. Но проблема в том, что тип свойства wrappedValue должен совпадать с типом в методе init.
Здесь на помощь приходит @autoclosure. Это позволяет передавать аргумент, который может быть как функцией () -> T, так и значением c типом T. Во втором случае значение будет завернуто в closure автоматически. Отсюда и название.
@propertyWrapper final class LazyOnce var wrappedValue: T if let existStorage = storage < return existStorage > let newStorage = lazyBlock() self.storage = newStorage return newStorage > private var storage: T? private let lazyBlock: () -> T init(wrappedValue: @escaping @autoclosure () -> T) self.lazyBlock = wrappedValue >>
Возможно вы обратили внимание, что я переименовал Lazy в LazyOnce. Это связанно с тем, что переменная помеченная таким модификатором будет создана лишь один раз. Нельзя присвоить ей другое значение за пределами @propertyWrapper.
На самом деле это баг и мы могли бы реализовать и другую версию похожую на lazy. Но давайте оставим у нашей реализации такую особенность.
final class Game @LazyOnce var help = HelpProvider()> let game = Game()// Cannot assign to property: 'help' is a get-only propertygame.help = HelpProvider()
Поддержка Dark Mode
И как обещал в самом начале, давайте рассмотрим более полезный пример применения @propertyWrapper в разработке.
Если вам нужно добавить поддержку темной темы, то вам необходимо описать логику выбора цвета. Для этого как раз подойдет @propertyWrapper. Я объявил аргумент dark опциональным, так как есть универсальные цвета, которые не меняются в зависимости от выбора светлой или темной темы.
@propertyWrapper struct AdaptiveColor private let light: UIColor private let dark: UIColor init(light: UIColor, dark: UIColor? = nil) self.light = light self.dark = dark ?? light > var wrappedValue: UIColor UIColor // Возвращаем цвет в зависимости от выбранной темы $0.userInterfaceStyle == .light ? self.light : self.dark > >>
Затем можно создать палитру и объявить в ней необходимые цвета. Используя @AdaptiveColor получаем очень компактное и простое представление.
final class Pallete @AdaptiveColor(light: .white, dark: .black) var backgroundColor: UIColor @AdaptiveColor(light: .green) var textColor: UIColor>
В конце хотелось посоветовать не изобретать @propertyWrapper только ради его применения. Да, это интересная возможность языка Swift. Но также этот подход часто требует использования синглтонов и это чаще плохо, чем хорошо. К примеру, @Injected с использованием синглтон контейнера внутри выглядит очень спорно.
Конец
- В этот раз мы узнали что такое lazy и зачем это нужно;
- На практике разобрались с @propertyWrapper;
- Написали почти бесполезную обертку @LazyOnce, которая уже есть — lazy;
- И даже одну полезную @AdaptiveColor.
На этом пока всё, я надеюсь пост был для вас интересным и полезным.
Подписывайтесь в Twitter и ещё увидимся.
© 2023. All rights reserved.
Swift — Lazy Var vs. Let при создании представлений программно (экономия памяти)
Я новичок, и я понимаю, что Lazy Var vs. Let. Я заметил, что он экономит массу использования памяти при использовании Lazy Var, особенно с ImageViews. Но уроки и руководства, которые я видел до сих пор, не часто используют Lazy Var, поэтому я чувствую подозрение, что это плохая практика и что я что-то пропускаю. Я сделал небольшое исследование и узнал, что Lazy не «потокобезопасен», но я не понимаю, что это значит. Я видел много плюсов и минусов, но я не могу делать никаких выводов, особенно потому, что у меня очень ограниченные знания. Когда все в порядке (или лучше) использовать Lazy Var vs. Let при создании UIView?
lazy var profileImageView: UIImageView = < let imageView = UIImageView(image: #imageLiteral(resourceName: "page1")) imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit return imageView >()
James Truong 18 нояб. 2017, в 16:36
Поделиться
Вот более старое (похоже, Swift 1) объяснение того, что такое lazy var . В частности, посмотрите на раздел о том, когда его использовать: mikebuss.com/2014/06/22/lazy-initialization-swift Теперь, просматривая три связанных ссылки в правом столбце вашего вопроса, которые предполагают, что они похожи на ваш I обнаружите, что никто на самом деле не отвечает на вопрос — как ссылка выше — о *, когда использовать каждый из них. Они либо объясняют, что такое lazy (и почему вы должны использовать var или почему что-то не получается. Я голосую за это, надеясь, что кто-то здесь может дать вам хороший ответ на хороший вопрос
dfd 18 нояб. 2017, в 15:45
Поделиться:
2 ответа
Лучший ответ
Будете ли вы использовать lazy var или нет, зависит от вашего кода и его контекста. Это не плохо или хорошо само по себе. Вы должны решить, когда это уместно.
Прежде чем вы сможете это решить, вы должны знать, что такое lazy var .
Что такое lazy var ?
Ленивая инициализация — это концепция, при которой инициализация (построение) переменной содержимого задерживается до ее первого использования. Первый доступ к такой переменной запускает инициализацию. Поскольку содержимое не создается до тех пор, пока переменная не будет использоваться (необходима), используя ленивые инициализированные переменные, можно сэкономить ресурсы.
Это основной диск за ленивой инициализацией. Вы не создаете что-то, пока оно вам не понадобится. Это также логика, которую вы будете использовать при принятии решения о том, должно ли что-то быть lazy var или нет.
Если вы имеете дело с представлениями (или чем-либо еще), которые всегда видны (необходимы), мало смысла использовать ленивую инициализацию. С другой стороны, когда вы имеете дело с экземплярами, которые не всегда нужны, то использование lazy var оправдано.
Если ваше представление всегда отображается в представленном контроллере представления, вы не сможете добиться большего, сделав его ленивым. Если это видно только при определенных обстоятельствах — например, когда пользователь расширяет какую-то сложенную панель, то делает ее ленивой имеет смысл. Это заставит ваш контроллер просмотра загружаться быстрее и использовать по умолчанию меньше памяти.
Что касается безопасности потока, lazy var не являются потокобезопасными в Swift.
Это означает, что если два разных потока попытаются получить доступ к одному и тому же lazy var в то же время, прежде чем такая переменная будет инициализирована, возможно, что один из потоков получит доступ к частично сконструированному экземпляру.
Подробнее о безопасности потоков вы найдете в: