Понимание жизненного цикла SwiftUI View и управления данными

Понимание жизненного цикла SwiftUI View и управления данными

10 января 2024 г.

Введение

Привет всем, меня зовут Максим Нечаев. Я старший iOS-разработчик в Snoonu, а также основатель собственного стартапа. Как и многие другие разработчики, я написал множество приложений и функций на UIKit. Все глобальные проекты, с которыми я работал, были связаны с этим. И неудивительно, ведь это наиболее распространенный набор элементов пользовательского интерфейса, если мы говорим о разработке для iOS. Наряду с UIKit следует сразу упомянуть макетирование через ограничение. Часто мы использовали SnapKit, чтобы упростить эту задачу и написать более минималистичный код.

Но времена менялись, и на горизонте появился новый, модный и модный фреймворк SwiftUI. Поначалу его сторонились. Они смотрели на это с презрением. Возможно, поначалу это даже имело смысл, потому что SwiftUI имел огромные ограничения и недостатки. Мы не смогли сделать и 50% того, что могли бы сделать с UIKit. Но спустя годы SwiftUI стал настолько мощным, что многие компании даже не обращают внимания на UIKit при запуске новых проектов. Почему?

Все потому, что SwiftUI имеет декларативный стиль верстки, использует удобные и быстрые реактивные элементы, а построение экрана любой сложности занимает от 30 минут до двух часов, тогда как на UIKit вы бы потратили минимум день. А что такое время для бизнеса? Мы все знаем, что время – деньги. Итак, первое, что SwiftUI уже делает сейчас, — это экономит деньги для бизнеса.

Давайте пойдем глубже. Что еще хорошего в SwiftUI? Простота создания анимации. Да-да, если представить себе крутое приложение с 60+ кадрами в секунду, которое плавно реагирует на тапы, кнопки сжимаются с анимацией, экраны выскакивают из правой части экрана, все происходит в красивой и приятной (для UX) анимации, все это очень быстро и легко сделать внутри SwiftUI. Честно говоря, многое из этого можно сделать и в UIKit. Но потратите гораздо больше сил и времени.

Надеюсь, мне удалось убедить вас в том, что SwiftUI готов к полноценной разработке и что вам следует изучить его прямо сейчас. Вот почему в моей первой статье на эту тему я хотел бы коснуться жизненного цикла SwiftUI View. Как создается «представление», где оно хранится и как обновляется, обо всем этом я хотел бы рассказать в этой статье. Давайте начнем!

Просмотр SwiftUI против UIView

Начнем с того, что UIView в большинстве случаев создается один раз. После этого мы просто обновляем его данные. Представление SwiftUI можно перерисовывать и создавать десятки раз на одном экране. Это очень хорошо показано на следующем изображении. Вы можете заметить, что когда мы используем UIViewRepresentable, структуру, которая помогает нам использовать UIView внутри SwiftUI, мы создаем ее один раз. Но далее, чтобы обновить его, мы тянем updateUIView и обновляем его столько раз, сколько необходимо.

SwiftUI View создается много раз, а UIView — один раз.

Хранение данных в представлении SwiftUI

Давайте рассмотрим способы хранения данных внутри Вид SwiftUI. Ведь вы, наверное, уже думаете, а как хранить данные, если View постоянно перерисовывается и перерисовывается?

В общей сложности мы можем разделить его на три части.

Первое. Let и var — классическое использование данных.

Второй. @State и @StateObject. Уже больше подхода SwiftUI

Третий. @Enviroment и @EnviromentObject

Пусть

Абсолютно классическое использование; мы можем создать какую-то константу, которую передаем наружу и используем внутри нашей структуры.

struct CustomView: View {
    let text: String

    init(text: String) {
        self.text = text
    }

    var body: some View {
        Text(text)
    }
}

Единственное, что здесь нужно иметь в виду, это то, что обычно, когда мы создаем константу, мы предполагаем, что она не изменится. Ситуация здесь такова, что когда CustomView изменяется и перерисовывается, константа text может быть изменена. То есть его можно «привязать» к изменяемым объектам.

Вар

И вот тут становится интересно. Что делать, если мы хотим изменить переменную внутри структуры? Мы всегда делали это очень просто, если говорить о классах в UIKit.

var text: String

Давайте представим, что мы сделали наш текст переменной. Кажется, теперь мы можем это изменить. Но нет, Свифт не позволит нам этого сделать. Структуры более капризны и жёстки, чем классы, и тем более в SwiftUI. Есть только один способ попытаться изменить текст — создать изменяющую функцию. Но, к сожалению, и здесь мы получим ошибку. Это будет выглядеть так: «Невозможно использовать мутирующий член для неизменяемого значения». В общем, идея с обычным var в структуре очень сомнительна, и стоит десять раз подумать, зачем вам это делать. Ведь в SwiftUI есть и другие классные инструменты для работы со значениями, о которых мы поговорим дальше.

Состояние

С государством ситуация в корне противоположная. Его можно и нужно использовать внутри структур, если мы хотим работать со значением и как-то его менять.

struct CustomView: View {
    @State private var text: String

    var body: some View {
        Text(text)
    }

    func changeText() {
        text = "Hello, World!"
    }
}

То есть, если мы создаем структуру с некоторыми локальными переменными, к которым мы привязываем локальную логику, нам следует использовать @State, чтобы все внутренние представления, использующие это значение, были обновлены. Все будет работать правильно, но... всегда есть «но». Что, если мы захотим установить значение извне?

Например, вот так

struct CustomView: View {
    @State private var text: String

    init(text: String) {
        self.text = text
    }

    var body: some View {
        Text(text)
    }
}

Как вы думаете, это сработает и в каких случаях? Честно говоря, это будет работать, но только тогда, когда мы установим первое значение. Когда мы установим все последующие значения, внутри body будет отображаться только первое.

Здесь важно понимать, что @State не существует так долго, как View. То есть, когда мы создаем @State внутри View, мы можем изменять его внутри View столько раз, сколько захотим. Представление будет обновлено и перерисовано, но локальное значение будет сохранено. Но если при этом мы попытаемся установить новые значения для @State через инициализатор, они будут просто проигнорированы.

Какой здесь результат? @State следует использовать только для сохранения и обновления локальных данных, а не тех, которые передаются извне в структуру.

StateObject

Теперь давайте поговорим об использовании StateObject. Обычно мы используем @StateObject, чтобы установить некую модель представления для нашего экрана. То есть мы соединяем его с классом, который подчиняется протоколу ObservableObject. Он работает очень похоже на @State, за одним исключением: он создается после первой инициализации структуры. То есть, хотя @State можно каким-то образом передать в init для помещения первого значения, @StateObject не будет работать. Он будет удален из памяти после вызова инициализатора и будет создан заново.

Окружающая среда

Перейдем к рассмотрению @Environment. Этот модификатор свойства используется для доступа к данным, которые передаются по иерархии представлений. В отличие от @StateObject, который используется для управления состоянием объекта в одном представлении, @Environment позволяет получить доступ к данным, которые являются общими для всего приложения или определенного часть этого.

Допустим, у вас есть какое-то значение, которое должно быть доступно во многих местах вашего интерфейса, например в пользовательских настройках или теме дизайна. Вместо того, чтобы передавать это значение по всем уровням иерархии, вы можете использовать @Environment для облегчения доступа.

Когда вы помечаете свойство в представлении SwiftUI как @Environment, SwiftUI автоматически ищет значение этого свойства в ближайшем подходящем контексте и предоставляет его вам. Если значение изменится, SwiftUI также автоматически обновит представления, использующие это значение. Это делает @Environment очень мощным инструментом для управления данными в SwiftUI.

Пример использования:

struct ContentView: View {
    @Environment(.locale) var locale: Locale

    var body: some View {
        Text("Current localization: (locale.identifier)")
    }
}

Объект Среды

Теперь давайте разберем @EnvironmentObject. Этот модификатор свойства используется для внедрения зависимостей в представления SwiftUI. В отличие от @Environment, который предназначен для доступа к системным настройкам или значениям, определенным на уровне приложения, @EnvironmentObject используется для передачи и использования пользовательских данных между представлениями.

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

Пример использования @EnvironmentObject:

class UserSettings: ObservableObject {
    @Published var score = 0
}

struct ContentView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text("Score: (settings.score)")
    }
}

Жизненный цикл представления SwiftUI

Понимание жизненного цикла представления в SwiftUI является ключом к созданию эффективных и производительных приложений. Давайте рассмотрим этот процесс подробно и интересно.

1. Инициализация (Инициализация)

Каждое представление SwiftUI начинает свой жизненный путь с инициализации. Это когда SwiftUI создает экземпляр вашего представления и устанавливает его начальное состояние. Важно понимать, что представления SwiftUI — это структуры, то есть типы значений. Когда вы создаете представление, вы фактически создаете его «снимок» в данный момент времени.

2. Обновление состояния (Обновление состояния)

После инициализации SwiftUI отслеживает изменения в данных, которые могут повлиять на ваше представление. Эти данные могут включать @State, @Binding, @ObservedObject, @EnvironmentObject и @Environment. . Когда одно из этих значений изменяется, SwiftUI запускает процесс обновления представления.

3. Вычисление тела (Вычисление тела)

На этом этапе SwiftUI вызывает свойство body вашего представления. body — это вычисляемое свойство, и каждый раз, когда SwiftUI обнаруживает изменение, влияющее на представление, он пересчитывает body. Этот процесс позволяет SwiftUI определить, какие части пользовательского интерфейса необходимо обновить.

4. Рендеринг и усиление; Макет (Рендеринг и макет)

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

5. Просмотр активации (Просмотр активации)

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

6. Деинициализация (Деинициализация)

Последним шагом в жизненном цикле представления является его деинициализация. Когда представление удаляется из иерархии представлений SwiftUI, оно деинициализируется. Это важное время для освобождения ресурсов или выполнения очистки.

Важные моменты:

  • Повторное использование. SwiftUI часто воссоздает и повторно использует представления. Поэтому важно избегать дорогостоящих операций в инициализаторах и body.
  • Эффективность: SwiftUI оптимизирован так, что обновляются только необходимые части интерфейса. Это повышает производительность и эффективность приложения.

Выводы

SwiftUI View сильно отличается от знакомого UIView, но в этом-то и суть. SwiftUI отличается, и это хорошо. В этой статье я постарался максимально кратко и понятно передать суть работы со SwiftUI View, ведь это ключевой навык для SwiftUI-разработчика. Вы встретите его в каждом задании, а значит, стоит разобраться в нем очень глубоко. Вам нужно осознавать, что вы пишете и создаете.

Желаю всем удачи на пути к тому, чтобы стать влиятельными разработчиками. Если вы читаете эту статью, то вы уже на правильном пути. Спасибо за уделенное время, всем пока!

Пожалуйста, ответьте на эту статью, чтобы я мог написать для вас больше полезного материала.


Оригинал