iOS и Swift — разработка приложений для iPhone и iPad
iOS — закрытая экосистема Apple со строгими правилами, но и с высоким качеством приложений. Разработка ведётся на языке Swift в среде Xcode: от первой экранной формы на SwiftUI до публикации в App Store. Курс разберёт все этапы пути.
1. Что такое iOS-разработка
iOS — операционная система для iPhone и iPad. Приложения компилируются в пакет .app и распространяются через App Store, где каждая сборка проходит проверку Apple. Официальный язык — Swift: современный, быстрый и безопасный по памяти.
Ключевые понятия: View — экран (в SwiftUI это View-структуры), ViewController — контроллер экрана в UIKit, AppDelegate — точка старта приложения. Современные проекты строятся на SwiftUI — декларативном описании интерфейса.
// Самая простая программа на Swift
print("Привет, iOS!")
// Тип с проверкой на этапе компиляции
let name: String = "Анна"
print(name.uppercased())NS (NSString, NSArray).2. Установка Xcode
Xcode — официальная среда разработки, которая устанавливается только из Mac App Store. Комплект весит несколько гигабайт, поэтому закладывай время на скачивание и первичную индексацию.
# Проверка после установки
xcodebuild -version
xcrun simctl list devicesПервая команда показывает версию Xcode, вторая — список доступных симуляторов. Для сборки в терминале может потребоваться команда sudo xcode-select --switch /Applications/Xcode.app.
3. Первый проект: структура
File → New → Project → iOS → App. Выбери интерфейс SwiftUI и язык Swift. Ключевые файлы проекта:
| Файл | Назначение |
|---|---|
MyApp.swift | точка входа с аннотацией @main |
ContentView.swift | стартовый экран приложения |
Assets.xcassets | иконки и картинки всех размеров |
Info.plist | настройки и разрешения приложения |
Project.pbxproj | структура проекта и настройки сборки |
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}.pbxproj нельзя редактировать руками — его перестраивает Xcode. Конфликты в нём при работе в команде решают через xcodeproj-совместимые инструменты.4. Основы Swift
Константы — через let, переменные — через var. Тип выводится автоматически. Отличительная черта языка — опциональные типы (optionals): значение может быть или есть, или отсутствовать (nil).
let name = "Анна" // константа, менять нельзя
var score = 0 // переменная
score += 10
func greet(_ name: String) -> String {
"Привет, \(name)!"
}
var maybe: String? = nil // опциональное значение
let length = maybe?.count ?? 0 // безопасный доступ + значение по умолчаниюОбращение к опциональному значению безопасно через ?., принудительно — через !. Второй путь короче, но упадёт с ошибкой, если значения нет.
! в рабочем коде: при неожиданном nil приложение мгновенно завершится. Это главная «бомба» Swift.5. Storyboard и SwiftUI
Два подхода к интерфейсу: классический Storyboard (визуальный редактор Xcode с файлом .storyboard) и современный SwiftUI (интерфейс описывается кодом). Новые приложения стоит писать на SwiftUI.
// SwiftUI — экран описывается декларативно
struct ContentView: View {
var body: some View {
VStack {
Text("Привет, SwiftUI!")
.font(.title)
Button("Нажать") {
print("Кнопка нажата")
}
}
.padding()
}
}.font(), .padding() — это цепочки настроек: SwiftUI собирает вид из «слоёв», а Xcode показывает предпросмотр рядом с кодом.В Storyboard-проекте контроллер называется UIViewController, а элементы перетаскиваются мышью в редактор. Оба подхода используют одни и те же базовые виджеты: UILabel/Text, UIButton/Button.
6. Таблицы и списки
Списки — основа большинства приложений. В SwiftUI это List, в UIKit — исторический UITableView с источником данных UITableViewDataSource.
struct Post: Identifiable {
let id = UUID()
let title: String
}
struct PostsView: View {
let posts = [
Post(title: "Новость первая"),
Post(title: "Новость вторая"),
]
var body: some View {
List(posts) { post in
Text(post.title)
}
}
}Протокол Identifiable требует уникальный id — по нему SwiftUI понимает, какой элемент обновился. Для вариантов ячеек подходит LazyVStack внутри ScrollView.
UITableView переиспользует ячейки через очередь — это важно для больших списков. В SwiftUI за производительность отвечает ленивый List, который делает то же самое автоматически.7. Навигация и переходы
Навигационный стек SwiftUI оборачивает экраны в NavigationStack, а переходы создаёт NavigationLink. Заголовок экрана задаётся модификатором .navigationTitle.
NavigationStack {
List(posts) { post in
NavigationLink(post.title) {
PostDetailView(post: post)
}
}
.navigationTitle("Новости")
}Возврат назад происходит автоматически кнопкой в навигебаре. Модальный экран поверх текущего открывается через .sheet:
.sheet(isPresented: $showForm) {
AddPostView()
}Знак $ создаёт двустороннюю связь (binding): изменение состояния в форме автоматически видно родительскому экрану.
NavigationStack — этого хватает большинству проектов.8. Работа с сетью
Сетевые запросы делаются классом URLSession, а JSON разбирается автоматически благодаря протоколу Codable.
struct Post: Codable {
let id: Int
let title: String
}
func fetchPosts() async throws -> [Post] {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Post].self, from: data)
}Вызвать функцию и показать результат можно так:
.task {
do {
posts = try await fetchPosts()
} catch {
print("Ошибка сети: \(error)")
}
}Info.plist (ключ NSAppTransportSecurity), но на релизе оставляй только HTTPS.Info.plist и объяснения пользователю.9. Хранилище данных: Core Data
Core Data — фреймворк Apple для хранения объектов в SQLite-базе. Модель описывается в файле .xcdatamodeld: сущности, атрибуты, связи. Контейнер загружается так:
let container = NSPersistentContainer(name: "MyModel")
container.loadPersistentStores { _, error in
if let error = error {
print("Ошибка загрузки: \(error)")
}
}Добавить и прочитать объект:
// Запись
let note = Note(context: viewContext)
note.text = "Купить продукты"
try? viewContext.save()
// Чтение через fetch request
let request = NSFetchRequest<Note>(entityName: "Note")
let notes = (try? viewContext.fetch(request)) ?? []Протокол NSFetchRequestResult позволяет запрашивать только нужные объекты, сортировать и группировать данные без загрузки всей базы.
10. Симулятор и отладка
Симулятор запускается кнопкой Run (треугольник) вверху Xcode. Он быстрее реального устройства, но не эмулирует камеру, жесты FaceID и некоторые датчики.
# Работа с симулятором из терминала
xcrun simctl boot "iPhone 15" # запустить устройство
xcrun simctl list devices # список доступных
xcrun simctl io booted screenshot shot.png # скриншот экранаОтладка включает: консоль (print и LLDB), точки останова с пошаговым проходом и инструмент Instruments для анализа памяти и производительности. Траблы тренируются через панель Debug Area внизу окна.
11. Публикация в App Store
Путь до пользователей: Product → Archive → распределение сборки → App Store Connect. Перед этим подписывается сертификат сборки и настраивается список устройств для тестирования через TestFlight.
# Автоматизация релиза через fastlane
fastlane sigh # скачать сертификаты и профили
fastlane beta # отправить сборку в TestFlight
fastlane deliver # загрузить метаданные и сборку12. Параллельность: async/await
Современная параллельность в Swift строится на ключевых словах async и await. Асинхронная функция не блокирует интерфейс: пока она ждёт ответ, пользователь продолжает взаимодействовать с приложением.
func loadAvatar() async throws -> UIImage {
let url = URL(string: "https://example.com/avatar.png")!
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw URLError(.badServerResponse)
}
return image
}
Task {
do {
avatarView.image = try await loadAvatar()
} catch {
print("Не удалось загрузить аватар: \(error)")
}
}Параллельный запуск нескольких задач даёт async let, а групповая работа — withThrowingTaskGroup.
await MainActor.run или оборачивай код в @MainActor.async let: ускорение почти линейное, а код остаётся таким же читаемым, как последовательный.13. Оптимизация приложения
Медленное приложение теряет пользователей быстрее, чем плохой дизайн. Первый шаг — найти узкие места встроенным инструментом Instruments (Product → Profile), а не оптимизировать «вслепую».
- Таблицы и списки: используй переиспользование ячеек и ленивую загрузку изображений.
- Память: ищи утечки по растущему графику в память — виновник чаще цикл ссылок между замыканием и self.
- Рендеринг: тяжёлые операции выноси из
bodyв фоновые задачи и кэшируй результат. - Сеть: объединяй мелкие запросы и не перезапрашивай данные при каждом экране.
В Instruments есть готовые шаблоны: Time Profiler для CPU, Allocations для памяти, Energy Log для батареи. Каждый профиль закрывает свой вопрос — измеряйте то, что реально беспокоит пользователя, а не всё подряд.
14. Тестирование: XCTest
Тесты ловят регрессии до релиза и позволяют рефакторить без страха. В Xcode создай target Unit Testing Bundle и пиши тесты на функции логики.
import XCTest
final class CartTests: XCTestCase {
func testAddIncreasesCount() {
var cart = Cart()
cart.add(item: "Молоко")
XCTAssertEqual(cart.count, 1)
}
}Запуск: Product → Test (⌘U). Полезные мастхэвы: XCTAssertEqual, XCTAssertThrowsError для проверки ошибок и setUp/tearDown для чистой среды между тестами.
Производительность тестов тоже важна: медленный набор отбивает желание запускать его. Держите логику быстрой, а сетевые проверки выносите в моки или фейковые ответы URLProtocol.
15. Совместная работа SwiftUI и UIKit
Старые приложения написаны на UIKit, новые — на SwiftUI, и их приходится смешивать. Swift напоминает, что оба фреймворка уживаются в одном проекте.
- SwiftUI → UIKit: обёртка
UIHostingControllerвстраивает SwiftUI-экран в существующий UIKit-иерархию. - UIKit → SwiftUI: протоколы
UIViewControllerRepresentableиUIViewRepresentableоборачивают старые контроллеры и виды для использования в SwiftUI-графе.
// Обёртка для UIKit-контроллера
struct CameraController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIImagePickerController {
UIImagePickerController()
}
func updateUIViewController(_ vc: UIImagePickerController, context: Context) {}
}
// Использование в SwiftUI
CameraController().ignoresSafeArea()Такой подход позволяет переносить проект постепенно: новые экраны на SwiftUI, а редкие UIKit-компоненты (камера, редактирование фото) оборачиваются точечно.
var onPhotoPicked: (UIImage) -> Void — так родительский SwiftUI-код остаётся декларативным.16. Безопасность iOS-приложения
Телефон — личное устройство, и пользователи ожидают, что данные защищены. Токены и пароли нельзя хранить в UserDefaults — они попадают в обычные файлы. Правильное место — Keychain.
// Сохранение в Keychain через SecItemAdd
let data = token.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "api_token",
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)- Keychain переживает переустановку приложения — токен нужно чистить вручную при выходе из аккаунта;
- Face ID / Touch ID подключаются через
LocalAuthentication— биометрия не хранит сами секреты, а подтверждает право их прочитать; - Сетевые секреты держи в Info.plist только для разработки (ATS), на релизе — строгий HTTPS.
print(token), утёкшая в краш-репорты, — это утечка данных.17. Камера, галерея и файлы
Запрос доступа к камере и фото — обязательный шаг в iOS: система покажет объяснение из Info.plist и спросит разрешение пользователя.
// Info.plist — объяснение причин
NSCameraUsageDescription -> "Нужно для добавления фото профиля"
NSPhotoLibraryUsageDescription -> "Нужно для выбора изображений"
// Проверка разрешения
import Photos
PHPhotoLibrary.requestAuthorization { status in
print(status == .authorized ? "Доступ ок" : "Доступ закрыт")
}- Добавь ключи в Info.plist — без них приложение просто упадёт при вызове API.
- Запроси статус
PHPhotoLibrary.requestAuthorizationи обработай отказ. - При отказе веди пользователя в Настройки:
UIApplication.openна страницу приложения.
Извлечение фото: PHImageManager.requestImage с указанием размера; для камеры — UIImagePickerController или кастомная сессия AVFoundation.
18. Мониторинг приложения в продакшене
Локальные запуски не показывают, что происходит на устройствах пользователей: краши, падения FPS и долгие запуски видны только в системах мониторинга.
- Crashlytics / Sentry — краш-логи с трейсом, символизация, отчёты по версиям;
- MetricKit — официальный фреймворк Apple: агрегирует CPU, память, запуски и падения без сторонних SDK;
- Firebase Analytics / Amplitude — события и воронки: открытия, экраны, покупки.
// Подключение MetricKit
import MetricKit
class MetricsDelegate: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
print(payload.cpuMetrics?.cumulativeCPUTime ?? 0)
}
}
}
MXMetricManager.shared.add(self)Стартовая стратегия: добавить краш-репорты и 3–5 ключевых событий (запуск, логин, покупка). Этого хватает, чтобы ловить регрессии, не закапываясь в миллионы метрик.
URLSessionMetric покажут, где теряются секунды — на сервере или на ответе клиента.19. SwiftData и миграции данных
SwiftData — современная замена Core Data: объявление модели через макрос @Model и минимум кода для хранения. Он удобен в новых проектах, где важен язык, а не legacy-стек.
import SwiftData
@Model
final class Task {
var title: String
var isDone: Bool
var createdAt: Date = Date()
init(title: String, isDone: Bool = false) {
self.title = title
self.isDone = isDone
}
}Миграции — боль любой базы. План: каждое изменение схемы — миграция; лёгкие (добавление поля) делаются автоматически, сложные (переименование, объединение) требуют явной логики.
| Изменение | Тип миграции |
|---|---|
| новое обязательное поле | лёгкая + дефолт в init |
| переименование атрибута | лёгкая с mapping |
| разделение одной сущности | тяжёлая, ручная |
20. Анимации и жесты в SwiftUI
Анимации в SwiftUI декларативны: вы описываете конечное состояние, а переход делает система. Вместе с жестами это основа «живого» интерфейса без ручных таймеров.
@State private var isExpanded = false
Button {
withAnimation(.spring(response: 0.35)) {
isExpanded.toggle()
}
} label: {
Image(systemName: "chevron.right")
.rotationEffect(.degrees(isExpanded ? 90 : 0))
}withAnimation— анимация изменений состояния;- жесты:
DragGesture,MagnificationGesture,LongPressGesture; - транзиции между экранами —
transition(_:)со значением.
Ключевой принцип: анимируйте изменения, а не «рестартуйте» вью. Для простых случаев хватает animation(_:value:) при изменении конкретного значения.
21. Combine: реактивные потоки
Combine — фреймворк для работы с потоком событий: таймеры, сеть, поля ввода, изменения пользователя. Поток описывается цепочкой операторов — это сильно отличается от императивного «вызвал и забыл».
import Combine
let search = searchField.textPublisher
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.map { $0.lowercased() }
.sink { query in
print("Ищем: \(query)")
}- операторы-«краны»: map, filter, debounce, merge, combineLatest;
sinkполучает результат,AnyCancellableудерживает подписку;- Combine беспощаден к ошибкам: значение ошибки — часть потока.
Практика: debounce для поиска, combineLatest для валидации формы, текущий токен + запрос к API через flatMap. Потоки легко тестировать — значения подаются вручную.
22. Локализация и String Catalog
Локализация — не перевод слов, а готовность приложения к другой культуре: числовые форматы, даты, валюты, направление текста. В Xcode 15+ переводы живут в едином String Catalog (.xcstrings).
// Localizable.xcstrings (фрагмент)
{
"items" : { "count": "товара" },
"hello" : "Привет, {name}!"
}| Аспект | Что учесть |
|---|---|
| Числа | 1 200,50 vs 1,200.5 |
| Даты | 12.09.2026 vs Sep 12, 2026 |
| Плюрализация | 1 товар / 2 товара / 5 товаров |
| RTL-языки | зеркальная вёрстка и выравнивание |
Правило: текст — всегда в ресурсах, никогда в коде. Строки параметризуются через безопасные интерполяции, а не среди выражений.
Что дальше
Углуби язык: описал базовый синтаксис — подтяни архитектуру через книги и реальные проекты. Для серверной интеграции пригодится курс REST API, для работы в команде — Git и GitHub. Сравнение подходов смотри в курсах Android (Kotlin), Flutter и React Native.