Мобильная разработка
Swift и SwiftUI
2014 год. WWDC. Apple тихо объявляет Swift - и за неделю он становится самым популярным языком на GitHub. К 2024-му Swift-экосистема: 2 миллиарда iPhone, 100 тысяч приложений в App Store, серверный Swift через Vapor. Весь этот мир собран на трёх идеях: optionals вместо null pointer, struct вместо class, async/await вместо callback.
- **App Store:** миллионы приложений написаны на Swift - от Instagram до банковских клиентов. SwiftUI ускоряет разработку в 2-3 раза по сравнению с UIKit
- **Server-side Swift:** Vapor и Hummingbird позволяют писать backend на Swift - один язык для iOS и сервера
- **Machine Learning:** Core ML + Swift позволяют запускать ML-модели прямо на устройстве, без обращения к серверу
- **@MainActor** решает задачу thread safety для UI-обновлений - ту же, что DispatchQueue.main.async в старом коде
Swift появился за 4 года до анонса - в секретном проекте Apple
Крис Лэттнер начал разрабатывать Swift в 2010 году как личный проект внутри Apple. Сначала никто, кроме него, не знал о его существовании. Позже Apple выделила команду, и работа продолжилась в секрете. На WWDC 2014 Swift был анонсирован публично - разработчики были шокированы: за один день кто-то создал новый язык программирования для их платформы. В 2015 Swift стал open source. В 2022 он вошёл в топ-20 TIOBE. Lattner позже ушёл в Google, затем основал компанию вокруг MLIR - но Swift остался его главным наследием.
Предварительные знания
Swift Basics
Swift - язык Apple, созданный в 2014 году как замена Objective-C. Ключевые принципы: **type safety**, **value semantics** (struct > class), **protocol-oriented programming**. Swift компилируется в нативный код через LLVM, обеспечивая производительность на уровне C++. Через 10 лет после релиза - шестой по популярности язык в мире по данным TIOBE.
**Ключевые особенности Swift:** - **Type Safety:** строгая типизация, ошибки на этапе компиляции, не в runtime - **Optionals:** nil-безопасность встроена в систему типов (String? vs String) - **Value Types:** struct вместо class по умолчанию (Copy-on-Write) - **Protocol-Oriented:** протоколы + extensions вместо наследования - **Memory:** ARC (Automatic Reference Counting), не garbage collection
Почему struct, а не class? В Swift struct - value type: копируется при присваивании. Это устраняет целый класс багов, связанных с неожиданным изменением разделяемого состояния. Apple рекомендует struct по умолчанию, class - только когда нужна идентичность или наследование.
ARC (Automatic Reference Counting) - управление памятью в Swift. Каждый объект (class instance) имеет счётчик ссылок. Когда счётчик = 0, объект освобождается. В отличие от garbage collector (Java/Kotlin), ARC детерминирован: объект удаляется мгновенно, без пауз. Но нужно следить за retain cycles (weak/unowned).
Чем Optionals в Swift решают проблему null pointer exception?
SwiftUI
SwiftUI - декларативный UI-фреймворк Apple (с 2019). Вместо «создай кнопку, задай текст, добавь на экран» описывается **что** должно быть на экране, а SwiftUI сам решает **как** это отрисовать и обновить. Та же идея, что React.js - Virtual DOM и diff-update. Только компилируется в нативный ARM код и работает без runtime-интерпретатора.
**SwiftUI: ключевые концепции:** - **Декларативный:** описываем UI как функцию от состояния - **body: some View** - вычисляемое свойство, описывающее UI - **@State** - локальное состояние View (value type) - **@Binding** - двусторонняя привязка к @State родителя - **@ObservedObject / @StateObject** - наблюдение за reference-type объектами - **@EnvironmentObject** - dependency injection через дерево View
SwiftUI использует **diffing**: при изменении @State фреймворк сравнивает старое и новое дерево View и обновляет только изменившиеся элементы. Это похоже на Virtual DOM в React, но реализовано на уровне компилятора через Value Types.
С iOS 17 появился макрос **@Observable**, упрощающий работу с состоянием. Больше не нужны @Published, ObservableObject, @StateObject - достаточно пометить класс @Observable и использовать обычные свойства. SwiftUI автоматически отслеживает зависимости.
В чём разница между @State и @StateObject в SwiftUI?
Combine
Combine - реактивный фреймворк Apple (с 2019). Паттерн Publisher/Subscriber: источник данных (Publisher) эмитит значения, а подписчик (Subscriber) их обрабатывает. Между ними - цепочка операторов для трансформации данных. Если знакомо RxJS или Kotlin Flow - Combine это то же самое, только с жёсткой статической типизацией компилятора.
**Combine: основные абстракции:** - **Publisher** - источник значений (может эмитить 0, 1 или много значений) - **Subscriber** - получатель значений - **Operator** - трансформация (map, filter, combineLatest, debounce, ...) - **Cancellable** - подписка, которую можно отменить - **@Published** - property wrapper, создающий Publisher из свойства
| Оператор | Что делает | Аналог в RxJS |
|---|---|---|
| map | Трансформация значений | map |
| filter | Фильтрация по условию | filter |
| debounce | Задержка (подождать паузу в эмиссии) | debounceTime |
| combineLatest | Комбинация последних значений нескольких Publisher | combineLatest |
| flatMap | Каждое значение - новый Publisher, объединяем результаты | switchMap/mergeMap |
| removeDuplicates | Убираем повторяющиеся значения | distinctUntilChanged |
Combine хорошо интегрируется со SwiftUI через @Published и ObservableObject. Однако с появлением async/await в Swift 5.5 многие задачи Combine можно решить проще. Рекомендация Apple: Combine для сложных реактивных потоков (debounce, combineLatest), async/await для простого асинхронного кода.
Важный нюанс: Combine - **типизированный** фреймворк. Каждый оператор меняет тип Publisher, и компилятор проверяет корректность цепочки. Ошибки ловятся при компиляции, а не в runtime. Цена - иногда сложные типы (AnyPublisher<[Item], Never>), которые нужно стирать через eraseToAnyPublisher().
Зачем в Combine-pipeline для поиска используется оператор debounce?
Async Await
Swift 5.5 (2021) принёс **structured concurrency** - async/await, Task, Actor. Это революция в асинхронном коде: вместо callback hell и цепочек Combine - линейный, читаемый код, который компилятор проверяет на безопасность. Kotlin Coroutines и Python asyncio решают ту же проблему тем же способом - асинхронность как последовательный синтаксис.
**Structured Concurrency в Swift:** - **async/await** - асинхронные функции с линейным потоком выполнения - **Task** - единица асинхронной работы (аналог корутины) - **TaskGroup** - параллельное выполнение нескольких задач - **Actor** - изоляция mutable state от data races (аналог serial queue) - **@MainActor** - гарантия выполнения на main thread
| Задача | Combine | async/await |
|---|---|---|
| Одиночный запрос | publisher.sink { ... } | let result = try await fetch() |
| Последовательные запросы | flatMap цепочка | await a(); await b() |
| Параллельные запросы | combineLatest/zip | async let / TaskGroup |
| Debounce поиска | debounce оператор | Task.sleep + отмена |
| Continuous stream | Publisher pipeline | AsyncSequence / AsyncStream |
| Data race protection | Combine serializes | Actor |
**Когда Combine, когда async/await?** Простые асинхронные операции (сеть, БД) - async/await. Сложные реактивные потоки (debounce, combineLatest, merge нескольких стримов) - Combine. На практике они прекрасно сочетаются: Combine для UI-биндингов, async/await для бизнес-логики.
Итог стека: Swift даёт три уровня инструментов - сам язык (type safety, optionals, value types), SwiftUI (декларативный UI), и async/await + Combine (безопасная асинхронность). Вместе они образуют стек, в котором большинство ошибок ловятся на этапе компиляции, а не в продакшене.
SwiftUI полностью заменил UIKit, и UIKit больше не нужен
SwiftUI покрывает ~90% типичных UI-задач, но UIKit по-прежнему необходим для сложных кастомных компонентов, тонкой настройки анимаций, работы с камерой и некоторых системных API. Apple использует UIViewRepresentable для интеграции UIKit в SwiftUI
SwiftUI - абстракция над UIKit (на iOS). Для стандартных элементов (списки, навигация, формы) SwiftUI проще и продуктивнее. Но для кастомного рендеринга (CALayer, Metal), сложных жестов, или интеграции со сторонними SDK на UIKit - UIKit незаменим. Большинство приложений используют оба фреймворка.
Что гарантирует Actor в Swift concurrency?
Ключевые идеи
- **Swift:** type safety (Optionals), value semantics (struct > class), protocol-oriented, ARC для памяти
- **SwiftUI:** декларативный UI, @State/@Binding/@StateObject для управления состоянием, diffing для эффективных обновлений
- **Combine:** реактивные потоки Publisher - Operator - Subscriber; лучше для complex streams (debounce, combineLatest)
- **async/await:** structured concurrency; Actor для data race safety; лучше для простых async-операций
Связанные темы
Swift/SwiftUI - один из двух главных нативных стеков:
- Жизненный цикл — SwiftUI управляет lifecycle через @Environment(\.scenePhase), @SceneStorage, .task { } modifier
- Нативная vs кроссплатформенная — Swift/SwiftUI - нативный подход для iOS; альтернатива - Flutter (Dart), React Native (TypeScript), KMP (Kotlin)
Вопросы для размышления
- Почему Apple выбрала value semantics (struct) как default вместо reference semantics (class)? Какие баги это предотвращает?
- Combine и async/await решают похожие задачи. Может ли async/await полностью заменить Combine в будущем?
- Как @MainActor связан с проблемой обновления UI из background thread, которую обсуждали в lifecycle?
Связанные уроки
- mob-02 — SwiftUI управляет lifecycle через @Environment и .task {}
- mob-04 — Навигация и routing в SwiftUI строятся поверх основ этого урока
- mob-01 — Нативный Swift/SwiftUI vs Flutter и React Native - trade-off платформ
- se-05 — SwiftUI diffing работает как паттерн Observer из GoF
- web-04 — React hooks и SwiftUI @State - одна архитектурная идея, разные платформы
- alg-01 — ARC и retain cycle detection требуют понимания графов зависимостей
- comp-01-intro