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