Мобильная разработка

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 остался его главным наследием.

Предварительные знания

  • Mobile App Lifecycle

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Комбинация последних значений нескольких PublishercombineLatest
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

ЗадачаCombineasync/await
Одиночный запросpublisher.sink { ... }let result = try await fetch()
Последовательные запросыflatMap цепочкаawait a(); await b()
Параллельные запросыcombineLatest/zipasync let / TaskGroup
Debounce поискаdebounce операторTask.sleep + отмена
Continuous streamPublisher pipelineAsyncSequence / AsyncStream
Data race protectionCombine serializesActor

**Когда 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
Swift и SwiftUI

0

1

Войти