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

Важные протоколы и классы 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 и инфраструктуру.
  • Такая декомпозиция улучшает тестируемость и снижает связность компонентов.

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