Нативная разработка iOS на Swift
Приложение крашит на cold start — EXC_BAD_ACCESS в момент инициализации синглтона, который обращается к другому синглтону, который ещё не инициализирован. Или: ViewController утечёт в памяти, потому что closure захватывает self без [weak self], и этот ViewController висит в памяти через два перехода после того, как пользователь его покинул. Это не гипотетические сценарии — это два самых частых класса проблем на iOS-проектах, которые приходят к нам после другой команды.
Нативная iOS-разработка на Swift — это прямой доступ к платформе. Без прослойки, без компромиссов по производительности, с полным контролем над тем, что происходит на каждом кадре.
SwiftUI vs UIKit: не холиварный вопрос, а инженерное решение
SwiftUI появился в 2019 году. К 2023–2024 он покрывает подавляющее большинство production-задач. Но UIKit не устарел и не исчезнет — Apple не deprecate-ит его, а продолжает добавлять API.
Реальная картина на крупных проектах: гибридный подход. SwiftUI для большинства экранов, UIKit там, где SwiftUI упирается в ограничения.
Где SwiftUI выигрывает безоговорочно
Декларативный синтаксис SwiftUI сокращает код UI в 3–5 раз по сравнению с UIKit. Экран настроек с List, Toggle, Picker — это 40 строк SwiftUI против 200 строк UIKit с делегатами UITableViewDataSource.
@State, @Binding, @ObservableObject (а с iOS 17 — макрос @Observable) создают реактивную связь между данными и UI без ручного reloadData(). Измение @State-переменной автоматически перерисовывает затронутую часть иерархии. Это работает правильно, если понимать, как SwiftUI вычисляет diff — через Equatable и id в ForEach.
AsyncImage, NavigationStack с типобезопасным роутингом через NavigationPath, searchable, refreshable — это готовые паттерны, которые UIKit требует реализовывать вручную.
Где UIKit по-прежнему необходим
UICollectionView с compositional layout и diffable data source — сложные сетки с разными типами ячеек, горизонтальными секциями внутри вертикального скролла, динамическими размерами ячеек. SwiftUI LazyVGrid / LazyHGrid не дают такого контроля.
Кастомные переходы между экранами. UIViewControllerAnimatedTransitioning и UIViewControllerInteractiveTransitioning — интерактивный pop gesture с частичным прогрессом, кастомный hero-переход с точным управлением frame. SwiftUI matchedGeometryEffect покрывает часть случаев, но не все.
UITextView с TextKit 2. Богатый редактор текста, кастомные атрибуты, кастомный рендеринг — TextKit 2 (iOS 16+) перешёл на async layout, что решило проблемы с производительностью на длинных документах. SwiftUI TextEditor — это обёртка вокруг UITextView без прямого доступа к TextKit.
UIScrollView с кастомным поведением. scrollViewDidScroll, parallax-эффекты, sticky headers с кастомной логикой, pull-to-refresh с кастомным индикатором. SwiftUI ScrollView с scrollPosition и onScrollGeometryChange (iOS 17) закрывает часть случаев, но не все.
Интеграция: UIHostingController и UIViewRepresentable
UIHostingController оборачивает SwiftUI-вью и делает его UIViewController. Встраиваем SwiftUI-экран в UIKit navigation stack — без проблем.
UIViewRepresentable делает обратное: оборачивает UIView для использования внутри SwiftUI. MapKit's MKMapView, WKWebView, кастомные UIKit-компоненты — всё это доступно в SwiftUI через этот протокол.
Один паттерн, который мы используем на проектах: UIKit-коорdinатор (Coordinator pattern) управляет навигацией на уровне флоу, а сами экраны реализованы на SwiftUI. Координатор создаёт UIHostingController, передаёт ему ViewModel через инициализатор или @EnvironmentObject, управляет переходами. Это даёт чистое разделение: SwiftUI занимается UI, Coordinator — навигацией.
Combine и async/await: оба живут в одном проекте
До Swift 5.5 асинхронный код на iOS строился на Combine или callback-цепочках. С появлением async/await и Actor модель конкурентности стала частью языка.
async/await + Actor: современный подход
// Правильно — MainActor гарантирует UI-обновления на main thread
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// handle error
}
}
}
@MainActor — это actor-изоляция, которая гарантирует выполнение на main thread. Без него @Published-свойства можно обновить с фонового потока, что приведёт к предупреждению Xcode и потенциальному крэшу.
Task { } запускает async-задачу из синхронного контекста. TaskGroup — параллельные задачи с агрегацией результатов. withCheckedThrowingContinuation — мост между callback API и async/await.
Где Combine остаётся полезным
Combine не заменён async/await — они дополняют друг друга. Combine эффективен для:
-
Дебаунсинг ввода.
searchTextField.textPublisher.debounce(for: .milliseconds(300), scheduler: RunLoop.main).sink { ... }— классический паттерн для поиска. -
Объединение нескольких источников данных.
Publishers.CombineLatestилиPublishers.Zipдля слияния двух Publishers в один. -
Операторы трансформации.
map,flatMap,filter,removeDuplicates— функциональная обработка потока значений.
На новых проектах мы используем async/await как основной инструмент для сетевых вызовов и бизнес-логики, Combine — для реактивной привязки UI-состояния там, где @Published + sink удобнее явного управления задачами.
Архитектура iOS-приложения
MVVM — базовый паттерн. ViewModel содержит логику и @Published-состояние, SwiftUI View подписывается через @ObservedObject или @StateObject. Правило одно: View не знает об URLSession, CoreData, UserDefaults.
Clean Architecture добавляет слои Repository и UseCase. UserRepository абстрагирует источник данных (сеть vs кеш). FetchUserUseCase содержит бизнес-правило. UserViewModel вызывает UseCase и управляет UI-состоянием. Каждый слой тестируется независимо через protocol-зависимости и mock-подмену.
TCA (The Composable Architecture) — более строгий паттерн от Point-Free. State, Action, Reducer, Effect — всё явное, всё тестируемое, composable через Scope. Хорошо работает в больших командах, где важна предсказуемость поведения и возможность тестировать каждый reducer изолированно.
Инструменты, без которых не обходится ни один релиз
Xcode Instruments. Time Profiler показывает, где CPU тратит время. Allocations — утечки памяти и excessive allocations. Leaks — объекты, которые не освобождаются. Перед каждым релизом — обязательный прогон.
Firebase Crashlytics. Crash-free rate, группировка по stack trace, breadcrumbs событий до крэша. Настраивается за 30 минут, даёт картину по всему парку устройств.
Fastlane match. Управление сертификатами и provisioning profiles через зашифрованный git-репозиторий. Устраняет «у меня локально собирается, а на CI нет» раз и навсегда.
XCTest + XCUITest. Unit-тесты для ViewModel и UseCase, UI-тесты для критичных флоу (онбординг, оплата, авторизация).
Процесс и сроки
Разработка iOS-приложения: анализ требований → архитектурное проектирование → UI/UX (параллельно) → разработка → тестирование (TestFlight) → App Store submission → поддержка.
App Store Review занимает 24–48 часов для стандартного приложения, до 7 дней при первой подаче или после rejection.
| Сложность | Ориентировочный срок |
|---|---|
| MVP (5–8 экранов, базовый API) | 6–10 недель |
| Среднее приложение (15–25 экранов) | 3–5 месяцев |
| Сложное (платежи, AR, CoreML, кастомный UI) | 5–9 месяцев |
Стоимость — индивидуально после анализа ТЗ и дизайна.







