Перейти к основному содержимому

Важные протоколы и классы Swift

Разработчику Архитектору

Важные протоколы и классы Swift​

1. Основные классы и структуры из стандартной библиотеки Swift​

String​

Тип String в Swift представляет собой коллекцию символов Unicode. Он является значимым типом (value type), реализует протоколы Collection, ExpressibleByStringLiteral, CustomStringConvertible и другие.

Свойства:

  • count — количество символов.
  • isEmpty — логическое значение, указывающее, пуста ли строка.
  • first, last — первый и последний символ (опциональные).
  • utf8, utf16, unicodeScalars — представления строки в различных кодировках.

Методы:

  • hasPrefix(_:), hasSuffix(_:) — проверка наличия префикса или суффикса.
  • components(separatedBy:) — разделение строки на подстроки по заданному разделителю.
  • replacingOccurrences(of:with:) — замена подстроки.
  • lowercased(), uppercased() — приведение к нижнему или верхнему регистру.

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

  • обработка пользовательского ввода;
  • парсинг текстовых данных;
  • форматирование сообщений.
let email = "user@example.com"
let domain = email.split(separator: "@").last.map(String.init) ?? ""
let normalized = email.trimmingCharacters(in: .whitespaces).lowercased()
print(domain, normalized.hasSuffix("@example.com"))

Разбор:

  • split(separator:) делит строку на части; last даёт домен после @.
  • map(String.init) превращает Substring в String для дальнейшей работы.
  • trimmingCharacters и lowercased() — типичная нормализация пользовательского ввода.
  • hasSuffix проверяет окончание без ручного сравнения индексов.

Array​

Array — упорядоченная коллекция значений одного типа. Это структура, а не класс, и она передаётся по значению.

Свойства:

  • count, isEmpty — аналогично строке.
  • first, last — доступ к первому и последнему элементу.
  • capacity — текущая ёмкость массива в памяти.

Методы:

  • append(_:), append(contentsOf:) — добавление элементов.
  • remove(at:), removeLast(), removeAll() — удаление.
  • contains(where:), first(where:), filter(_:) — поиск и фильтрация.
  • map(_:), compactMap(_:), flatMap(_:) — трансформация.
  • sorted(by:), shuffle() — сортировка и перемешивание.

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

  • хранение списков данных;
  • работа с результатами API;
  • кэширование объектов.
var scores = [90, 40, 75]
scores.append(88)
scores.sort()
let passed = scores.filter { $0 >= 60 }
print(passed.last ?? 0)

Разбор:

  • var нужен, потому что массив изменяется (append, sort).
  • sort() упорядочивает элементы на месте по возрастанию.
  • filter возвращает новый массив только с оценками >= 60.
  • last ?? 0 безопасно читает последний элемент или подставляет 0, если массив пуст.

Dictionary​

Dictionary — неупорядоченная коллекция пар "ключ–значение". Ключи должны соответствовать протоколу Hashable.

Свойства:

  • count, isEmpty.
  • keys, values — коллекции ключей и значений.

Методы:

  • updateValue(_:forKey:) — обновление или вставка значения.
  • removeValue(forKey:) — удаление по ключу.
  • объединение словарей.
merge(_:uniquingKeysWith:)

Разбор:

  • Это сигнатура метода Dictionary.merge, который объединяет два словаря.
  • Параметр uniquingKeysWith — замыкание, определяющее, какое значение оставить при конфликте одинаковых ключей.
  • Метод полезен при слиянии конфигураций, кешей и ответов из нескольких источников.

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

  • конфигурации;
  • кэширование;
  • маппинг идентификаторов на объекты.
var usersByID: [Int: String] = [1: "Аня", 2: "Борис"]
usersByID[3] = "Вера"
usersByID.updateValue("Анна", forKey: 1)
if let name = usersByID[2] {
print(name)
}

Разбор:

  • Литерал [ключ: значение] создаёт словарь с начальными парами.
  • Присваивание usersByID[3] = ... вставляет или заменяет значение по ключу.
  • updateValue(_:forKey:) явно обновляет существующий ключ и возвращает старое значение (optional).
  • Чтение usersByID[2] даёт String?: ключа может не быть.

Set​

Set — неупорядоченная коллекция уникальных значений, соответствующих протоколу Hashable.

Свойства и методы:

  • , remove(_:), contains(_:).
insert(_:)

Разбор:

  • insert(_:) добавляет элемент в Set.
  • Если значение уже существует, множество не создаёт дубликат.
  • Это ключевой плюс Set по сравнению с Array для задач "уникальности".
  • Операции над множествами — union(_:), intersection(_:), subtracting(_:), symmetricDifference(_:).

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

  • отслеживание уникальных событий;
  • проверка принадлежности;
  • исключение дубликатов.
var tags: Set<String> = ["swift", "ios"]
tags.insert("swift") // дубликат не добавится
tags.insert("ui")
let alsoSwift = tags.contains("swift")
let both = tags.union(["android", "ios"])
print(alsoSwift, both.count)

Разбор:

  • Set хранит только уникальные элементы; повторный insert("swift") игнорируется.
  • contains проверяет принадлежность за амортизированное O(1) в среднем.
  • union объединяет два множества без дубликатов.
  • Удобно для тегов, посещённых id, прав доступа.

2. Ключевые классы из Foundation​

Foundation — это фундаментальный фреймворк Apple, предоставляющий базовые типы данных, операции с файловой системой, сетевыми запросами, датами и многим другим.


NSObject​

Базовый класс для всех Objective-C совместимых объектов. В Swift многие классы наследуются от NSObject, особенно при работе с UIKit или Cocoa.

Свойства и методы:

  • isEqual(_:) — сравнение объектов.
  • hash — хэш-значение.
  • description — текстовое представление объекта.
  • performSelector(_:) — динамический вызов метода (редко используется в чистом Swift).

Применяется при необходимости совместимости с Objective-C API, например, при использовании UserDefaults, NotificationCenter, NSCoding.


URL​

Представляет унифицированный ресурсный локатор. Используется для работы с файлами, сетевыми адресами, путями.

Свойства:

  • absoluteString, path, host, scheme, query — компоненты URL.
  • deletingLastPathComponent(), appendingPathComponent(_:) — манипуляции с путём.

Пример использования — построение запросов к API, работа с локальными файлами, обработка deep links.


Date и Calendar​

Date представляет момент времени независимо от часового пояса. Calendar предоставляет методы для работы с календарными компонентами — год, месяц, день, час и т.д.

Методы Calendar:

  • dateComponents(_:from:) — извлечение компонентов из даты.
  • date(from:) — создание даты из компонентов.
  • isDateInToday(_:), isDate(_:_:toGranularity:) — сравнение дат.

Пример использования — отображение событий по датам, планирование уведомлений, анализ временных меток.


FileManager​

Управляет файловой системой — создание, удаление, перемещение файлов и директорий.

Методы:

  • fileExists(atPath:) — проверка существования файла.
  • contentsOfDirectory(atPath:) — получение списка файлов в папке.
  • createDirectory(atPath:withIntermediateDirectories:attributes:) — создание директории.
  • copyItem(at:to:), moveItem(at:to:), removeItem(at:) — операции с файлами.

Пример использования — сохранение кэша, экспорт данных, работа с документами пользователя.


JSONEncoder / JSONDecoder​

Инструменты для сериализации и десериализации объектов в/из JSON. Требуют, чтобы типы соответствовали протоколу Codable.

Пример:

struct User: Codable {
let name: String
let age: Int
}

let user = User(name: "Timur", age: 31)
let encoder = JSONEncoder()
let data = try encoder.encode(user)
let decoder = JSONDecoder()
let restored = try decoder.decode(User.self, from: Data)

Разбор:

  • struct User — Codable означает, что тип может быть и закодирован, и декодирован.
  • JSONEncoder().encode(user) превращает модель в Data для передачи/сохранения.
  • JSONDecoder().decode(User.self, from: ...) восстанавливает объект из байтов.
  • Пара try указывает на возможные ошибки сериализации (невалидные данные, несоответствие схемы).

Применяется при работе с REST API, сохранении состояния, миграции данных.


3. Протоколы (интерфейсы) Swift​

Swift делает акцент на протоколах как на основном механизме абстракции. Протоколы определяют контракт, которому должен соответствовать тип.


Equatable​

Типы, реализующие Equatable, могут сравниваться с помощью оператора ==.

Пример:

struct Point: Equatable {
let x, y: Int
}
// Автоматическая реализация == доступна, если все свойства Equatable

Разбор:

  • Equatable даёт возможность сравнивать два Point через ==.
  • Компилятор синтезирует реализацию автоматически, потому что все поля (Int) тоже Equatable.
  • Это снижает boilerplate и делает код моделей компактнее.

Comparable​

Расширяет Equatable, добавляя возможность упорядочения (<, >, <=, >=).

Пример: сортировка массивов, бинарный поиск.


Hashable​

Необходим для использования типа в качестве ключа в Dictionary или элемента в Set. Требует корректной реализации hash(into:) или автоматической генерации.


Codable​

Объединяет Encodable и Decodable. Позволяет легко сериализовать структуры и классы.


CustomStringConvertible​

Позволяет определить пользовательское текстовое представление через свойство description.


Identifiable​

Требует наличие уникального id. Широко используется в SwiftUI для отслеживания изменений в списках.


4. Часто используемые классы в прикладной разработке​

UIViewController (UIKit)​

Базовый класс контроллера представления в UIKit. Управляет жизненным циклом экрана:

  • viewDidLoad() — вызывается после загрузки иерархии вью.
  • viewWillAppear(_:), viewDidAppear(_:) — перед и после появления экрана.
  • viewWillDisappear(_:), viewDidDisappear(_:) — перед и после исчезновения.

Свойства:

  • view — корневое представление контроллера.
  • navigationController, tabBarController — ссылки на родительские контейнеры.

Применяется в каждом экране приложения, построенного на UIKit.


View (SwiftUI)​

В SwiftUI всё строится на декларативных представлениях, соответствующих протоколу View.

Пример:

struct ContentView: View {
var body: some View {
Text("Hello")
}
}

Разбор:

  • struct ...: View объявляет SwiftUI-представление.
  • Свойство body возвращает дерево UI-элементов, в данном случае Text("Hello").
  • SwiftUI обновляет интерфейс на основе состояния, пересчитывая body при изменениях.

Ключевые модификаторы — padding(), frame(), onAppear(), navigationTitle(_:).


ObservableObject и @Published​

Используются для управления состоянием в SwiftUI. Класс, соответствующий ObservableObject, может публиковать изменения через свойства с аннотацией @Published.

Пример:

class AppState: ObservableObject {
@Published var isLoggedIn = false
}

Разбор:

  • ObservableObject делает объект источником состояния для SwiftUI.
  • @Published автоматически отправляет событие при изменении isLoggedIn.
  • Представления, подписанные на этот объект, перерисуются при смене значения.

В представлении используется @ObservedObject или @StateObject.


URLSession​

Основной инструмент для выполнения сетевых запросов.

Методы:

  • dataTask(with:completionHandler:) — асинхронный запрос данных.
  • downloadTask(with:completionHandler:) — загрузка файла.
  • uploadTask(with:from:completionHandler:) — отправка данных.

Пример:

let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// Обработка ответа
}
task.resume()

Разбор:

  • URL(...)! создаёт URL и принудительно распаковывает optional (в продакшене лучше безопасная обработка).
  • dataTask(with:) создаёт асинхронный HTTP-запрос с callback.
  • task.resume() запускает задачу: без этого вызова сетевой запрос не отправится.

Применяется в любом приложении, взаимодействующем с сервером.


NotificationCenter​

Позволяет реализовать паттерн "наблюдатель" (observer).

Методы:

  • addObserver(forName:object:queue:using:) — подписка на уведомление.
  • post(name:object:userInfo:) — отправка уведомления.

Пример: реакция на смену темы, обновление данных после завершения фоновой задачи.


UserDefaults​

Хранение небольших объёмов данных в виде пар "ключ–значение". Поддерживает примитивы, Data, URL, Array и Dictionary из поддерживаемых типов.

Методы:

  • set(_:forKey:), string(forKey:), bool(forKey:), synchronize().

Пример — сохранение настроек, флага первого запуска, токена авторизации.


5. Практические сценарии и выбор инструментов​

Сценарий 1: Загрузка и отображение списка пользователей

  • Используется URLSession для запроса к API.
  • Ответ десериализуется через JSONDecoder в массив структур, соответствующих Codable.
  • Данные передаются в UITableView (UIKit) или List (SwiftUI).
  • Для обновления интерфейса применяется DispatchQueue.main.async или @Published + ObservableObject.

Сценарий 2: Сохранение состояния между запусками

  • Небольшие данные (например, настройки) — UserDefaults.
  • Сложные структуры — сериализация в Data через JSONEncoder и сохранение в файл через FileManager.

Сценарий 3: Реакция на системные события

  • Подписка на UIApplication.willEnterForegroundNotification через NotificationCenter.
  • Обновление UI или данных при возвращении в приложение.

Сценарий 4: Работа с датами и временем

  • Использование DateFormatter для отображения дат в локализованном виде.
  • Calendar для вычисления разницы между датами или определения дня недели.

6. Работа с асинхронностью и потоками выполнения​

Swift предоставляет мощные инструменты для управления параллельным и асинхронным кодом. Современные приложения почти всегда взаимодействуют с сетью, файловой системой или выполняют вычисления, которые не должны блокировать основной поток пользовательского интерфейса.


DispatchQueue (Grand Central Dispatch)​

DispatchQueue — это механизм Apple для управления очередями задач. Он позволяет выполнять код синхронно или асинхронно на глобальных, пользовательских или главном потоке.

Основные типы очередей:

  • Main queue — привязана к главному потоку, используется для обновления UI.
  • Global queues — системные фоновые очереди с разными уровнями приоритета (userInteractive, userInitiated, default, utility, background).
  • Custom serial/concurrent queues — создаваемые разработчиком последовательные или параллельные очереди.

Методы:

  • async(execute:) — асинхронное выполнение замыкания.
  • sync(execute:) — синхронное выполнение (блокирует текущий поток до завершения).
  • asyncAfter(deadline:execute:) — отложенное выполнение.

Пример:

DispatchQueue.global(qos: .userInitiated).async {
// Тяжёлая операция: парсинг, загрузка, расчёт
let result = performExpensiveCalculation()

DispatchQueue.main.async {
// Обновление интерфейса
label.text = "Результат: \(result)"
}
}

Разбор:

  • Блок отправляется в глобальную фоновую очередь с приоритетом userInitiated.
  • Тяжёлое вычисление выполняется вне главного потока, чтобы UI не зависал.
  • DispatchQueue.main.async возвращает обновление интерфейса на главный поток.
  • Это классический шаблон GCD для разделения вычислений и UI.

async/await (начиная с Swift 5.5)​

Swift внедрил современную модель асинхронного программирования на основе ключевых слов async и await. Это позволяет писать асинхронный код в линейном стиле без вложенных замыканий.

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

Пример:

Код ITЗагрузка примера кода…

Разбор:

  • async throws показывает, что функция и ждёт I/O, и может завершиться ошибкой.
  • try await URLSession.shared.data(from:) получает данные современным async-API.
  • Оборачивание вызова в Task создаёт асинхронный контекст для использования await.
  • do/catch разделяет успешный путь и обработку ошибок сети.

Task и TaskGroup​

Task — это единица асинхронной работы. TaskGroup позволяет запускать несколько задач параллельно и собирать их результаты.

Пример параллельной загрузки:

let urls = [url1, url2, url3]
let results = await withTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
return try await fetchData(from: url)
}
}
return await group.reduce(into: []) { $0.append($1) }
}

Разбор:

  • withTaskGroup запускает несколько загрузок параллельно для каждого URL.
  • group.addTask добавляет дочернюю задачу на каждый элемент массива.
  • reduce(into:) собирает результаты в единый массив Data.
  • Подход ускоряет пакетные запросы по сравнению с последовательной загрузкой.

Эти инструменты особенно полезны при работе с множественными API-запросами, фоновой обработкой изображений или синхронизацией данных.


7. Работа с уведомлениями и событиями​

NotificationCenter​

Как уже упоминалось, NotificationCenter реализует паттерн "наблюдатель". Он позволяет отправлять и получать уведомления по имени.

Часто используемые системные уведомления:

  • UIApplication.willEnterForegroundNotification
  • UIApplication.didBecomeActiveNotification
  • UIContentSizeCategory.didChangeNotification — изменение размера шрифта в системе

Пример подписки:

let observer = NotificationCenter.default.addObserver(
forName: UIDevice.batteryLevelDidChangeNotification,
object: nil,
queue: .main
) { _ in
print("Уровень заряда изменился")
}

Разбор:

  • Код подписывается на системное уведомление об изменении заряда устройства.
  • queue: .main гарантирует, что обработчик выполнится на главном потоке.
  • Возвращаемый observer нужно хранить и удалять при завершении жизненного цикла объекта.

Важно отписываться от уведомлений во избежание утечек памяти, особенно в UIViewController — в методе deinit или viewWillDisappear.


Combine Framework​

Начиная с iOS 13, Apple представила фреймворк Combine — реактивное программирование на основе издателей (Publisher) и подписчиков (Subscriber).

Ключевые протоколы:

  • Publisher — генерирует значения во времени.
  • Subscriber — получает значения и завершение.

Часто используемые издатели:

  • @Published — автоматически создаёт Published издатель при изменении свойства.
  • URLSession.dataTaskPublisher — возвращает данные из сети как поток.
  • Timer.publish(every:tolerance:on:in:options:) — периодическая генерация событий.

Операторы трансформации:

  • map, filter, compactMap
  • debounce, throttle — ограничение частоты событий
  • receive(on:) — переключение очереди выполнения
  • sink — подписка на значения

Пример:

Код ITЗагрузка примера кода…

Разбор:

  • $userName — publisher, генерируемый @Published для изменения поля ввода.
  • debounce ждёт паузу в вводе, removeDuplicates отсекает повторные значения.
  • sink подписывается на поток и выполняет действие (поиск) при новых значениях.
  • store(in:) удерживает подписку, чтобы она не деаллоцировалась раньше времени.

Combine особенно эффективен в связке с SwiftUI, где изменения состояния автоматически приводят к перерисовке интерфейса.


8. Работа с локализацией и ресурсами​

Bundle​

Класс Bundle предоставляет доступ к ресурсам приложения — изображениям, звукам, локализованным строкам, файлам конфигурации.

Методы:

  • url(forResource:withExtension:) — получение URL файла по имени и расширению.
  • path(forResource:ofType:) — путь к ресурсу.
  • localizedString(forKey:value:table:) — получение локализованной строки.

Пример:

if let path = Bundle.main.path(forResource: "config", ofType: "json") {
let data = try Data(contentsOf: URL(fileURLWithPath: path))
// Парсинг конфигурации
}

Разбор:

  • Bundle.main.path(...) ищет файл ресурса внутри пакета приложения.
  • if let безопасно проверяет, что путь действительно найден.
  • Data(contentsOf:) читает файл в память для дальнейшего парсинга JSON.

LocalizedStringKey и NSLocalizedString​

В SwiftUI используется Text("Hello"), где строка автоматически интерпретируется как ключ локализации. В UIKit применяется NSLocalizedString("greeting", comment: "").

Файлы локализации (Localizable.strings) содержат пары:

"greeting" = "Приветствие";
"settings_title" = "Настройки";

Разбор:

  • Это формат файла Localizable.strings: пары ключ = значение.
  • Ключи используются в коде (NSLocalizedString/Text), значения показываются пользователю.
  • Подход отделяет текст интерфейса от логики и упрощает мультиязычность.

Поддержка нескольких языков достигается через папки ru.lproj, en.lproj и т.д.


9. Безопасность и работа с ключами​

Keychain Services​

Для хранения чувствительных данных (токены, пароли, ключи шифрования) используется Keychain, а не UserDefaults.

Swift-обёртки (например, KeychainAccess или собственные решения) упрощают работу с низкоуровневым C API.

Пример сохранения:

let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "authToken",
kSecValueData as String: token.data(using: .utf8)!
]
SecItemAdd(query as CFDictionary, nil)

Разбор:

  • Словарь query описывает атрибуты записи в Keychain (класс, аккаунт, данные).
  • kSecValueData содержит бинарное представление токена.
  • SecItemAdd сохраняет запись в защищённое хранилище устройства.

Получение:

let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "authToken",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
SecItemCopyMatching(query as CFDictionary, &result)
if let data = result as? Data, let token = String(data: Data, encoding: .utf8) {
// Использование токена
}

Разбор:

  • В запросе чтения kSecReturnData: true просит вернуть содержимое записи.
  • SecItemCopyMatching ищет первый подходящий элемент (kSecMatchLimitOne).
  • Результат приводится к Data, затем декодируется в строку токена.
  • Важно: в строке String(data: Data, ...) должна использоваться переменная data, иначе это ошибка примера.

Keychain обеспечивает шифрование на уровне устройства и сохраняет данные даже после удаления приложения (если не указано иное).


10. Архитектурные паттерны и организация кода​

Хотя Swift сам по себе не диктует архитектуру, в экосистеме Apple сложились устоявшиеся подходы:

  • MVC (Model-View-Controller) — стандарт UIKit, где UIViewController управляет логикой и представлением.
  • MVVM (Model-View-ViewModel) — популярен в SwiftUI, где ViewModel (часто ObservableObject) содержит состояние и логику, а View — только отображение.
  • Coordinator — паттерн для управления навигацией, особенно в UIKit.

Ключевые принципы:

  • Разделение ответственности.
  • Минимизация логики в View.
  • Использование протоколов для зависимостей.
  • Изоляция сетевого слоя (NetworkService), слоя данных (Repository), бизнес-логики (UseCase).

Пример структуры проекта:

Sources/
├── Models/
│ └── User.swift
├── Views/
│ └── ProfileView.swift
├── ViewModels/
│ └── ProfileViewModel.swift
├── Services/
│ ├── NetworkService.swift
│ └── AuthService.swift
├── Repositories/
│ └── UserRepository.swift
└── Utils/
└── Extensions.swift

Разбор:

  • Это пример модульной структуры проекта по слоям ответственности.
  • Models, Views, ViewModels, Services, Repositories разделяют домен, UI и инфраструктуру.
  • Такая декомпозиция улучшает тестируемость и снижает связность компонентов.

Такая организация повышает тестируемость, читаемость и поддерживаемость.