Swift — Lazy Var vs. Let при программном создании представлений (экономия памяти)

Я новичок и вроде как понимаю Lazy Var vs. Let. Я заметил, что это экономит массу памяти при использовании Lazy Var, особенно с ImageViews. Но учебники и руководства, которые я видел до сих пор, не очень часто используют Lazy Var, поэтому я подозреваю, что это плохая практика и что я что-то упускаю из виду.

Я провел небольшое исследование и узнал, что Lazy не является "потокобезопасным", но я не понимаю, что это значит. Я видел много плюсов и минусов, но не могу сделать никаких выводов, тем более, что у меня очень ограниченные знания.

Когда можно (или лучше) использовать Lazy Var вместо Let при создании UIView?

lazy var profileImageView: UIImageView = {

    let imageView = UIImageView(image: #imageLiteral(resourceName: "page1"))
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    return imageView

}()

person Jazure    schedule 18.11.2017    source источник
comment
Вот более старое (похоже, Swift 1) объяснение того, что такое lazy var. В частности, посмотрите раздел о том, когда его использовать: mikebuss.com/2014/ 22/06/lazy-initialization-swift Теперь, просматривая три связанные ссылки в правой колонке вашего вопроса, которые предполагают, что они похожи на ваши, я обнаружил, что ни одна действительно ответьте на вопрос - как ссылка выше - о *когда использовать каждый из них. Они либо объясняют, что такое lazy (и почему вы должны использовать var, или почему что-то не строится. Я голосую за это, надеясь, что кто-то здесь может дать вам хороший ответ на хороший вопрос   -  person dfd    schedule 18.11.2017


Ответы (3)


Будете ли вы использовать lazy var или нет, зависит от вашего кода и его контекста. Это не плохо и не хорошо само по себе. Вы должны решить, когда это уместно.

Прежде чем вы сможете это решить, вы должны знать, что такое lazy var.

Что такое lazy var?

Ленивая инициализация — это концепция, при которой инициализация (конструкция) содержимого переменной откладывается до ее первого использования. Первый доступ к такой переменной запускает инициализацию. Поскольку контент не создается до тех пор, пока переменная не используется (необходима), использование ленивых инициализированных переменных может сэкономить ресурсы.

Это основная причина ленивой инициализации. Вы не создаете что-то, пока вам это не нужно. Это также логика, которую вы будете использовать при принятии решения о том, должно ли что-то быть lazy var или нет.

Если вы имеете дело с представлениями (или чем-то еще), которые всегда видны (необходимы), нет смысла использовать ленивую инициализацию. С другой стороны, когда вы имеете дело с экземплярами, которые не всегда нужны, использование lazy var оправдано.

Если ваше представление всегда видно в представленном контроллере представления, вы не добьетесь многого, сделав его ленивым. Если он виден только при определенных обстоятельствах — например, когда пользователь разворачивает какую-то свернутую панель — тогда имеет смысл сделать его ленивым. Это ускорит загрузку вашего контроллера представления и по умолчанию будет использовать меньше памяти.


Что касается безопасности потоков, lazy var не являются потокобезопасными в Swift.

Это означает, что если два разных потока попытаются получить доступ к одному и тому же lazy var одновременно, до того, как такая переменная будет инициализирована, возможно, что один из потоков получит доступ к частично сконструированному экземпляру.

Вы можете найти больше о безопасности потоков в:

Swift - является ли ленивый var потокобезопасным?

Сделать "ленивую переменную" потокобезопасной

person Dalija Prasnikar    schedule 18.11.2017

Еще одним преимуществом использования lazy var является улучшение читабельности вашего кода.

В вашем примере код, связанный с представлением изображения, сгруппирован вместе, а не распределен по инициализатору, функции настройки или viewDidLoad. Это улучшает локальные рассуждения, не требуя, чтобы читатель кода заходил в разные места кода, чтобы понять, как настроено ваше представление. Чтобы узнать о вашем представлении, им нужно всего лишь перейти к его объявлению.

Закрытие инициализации, помеченное как lazy var, может получить доступ к self, что позволяет выполнять дополнительную настройку внутри замыкания, например добавлять целевые действия или ссылаться на другие константные свойства.

Я бы посчитал хорошей практикой инициализацию свойств (особенно представлений) замыканиями в виде lazy var, и, похоже, это также набирает популярность в сообществе Swift.

В зависимости от проекта экономия времени разработчика может быть гораздо более ценной, чем экономия системной памяти.

person nathangitter    schedule 18.11.2017

Использование ленивых переменных может обеспечить обход парадоксальной проблемы: вы хотите создать пользовательское представление с подпредставлениями, инициализация которых ссылается на родительское представление.

Например, если вы хотите создать подкласс UIView, содержащий дочерний UIScrollView того же размера, вы не можете объявить класс, содержащий:

var m_scrollView: UIScrollView

override init(frame: CGRect)
{
    m_scrollView = UIScrollView(frame: self.frame)
    super.init(frame: frame)
}

Компилятор будет жаловаться, что вы ссылаетесь на self перед вызовом super.init. Но... super.init нужно вызывать после инициализации всех элементов.

Решение этой круговой проблемы делает m_scrollView ленивым и инициализирует его в объявлении:

lazy var m_scrollView = UIScrollView(frame: self.frame)
person Oscar    schedule 11.05.2019
comment
Иногда я склонен использовать lazy в таких случаях. Однако я не уверен, что это может привести к каким-то странным ошибкам позже. Я бы предпочел добавить метод настройки для UIScrollView с использованием ограничений вместо использования здесь отложенной инициализации. - person laka; 29.01.2021