1. Не блокируйте Main Thread
Это, вероятно, самое важное правило производительности в iOS-разработке.
Main thread отвечает за:
- рендеринг UI
- анимации
- обработку касаний
- обновление экрана
Если тяжёлая работа выполняется там — приложение сразу начинает ощущаться медленным.
Частая ошибка:
func loadUsers() {
let data = try! Data(contentsOf: url)
let users = try! JSONDecoder().decode([User].self, from: data)
self.users = users
tableView.reloadData()
}
Здесь всё выполняется на main thread:
- сетевой запрос
- парсинг JSON
- обновление UI
Это блокирует рендеринг.
Пользователь ощущает:
- лаги интерфейса
- подвисания взаимодействий
- задержки при скролле
Более правильный подход:
func loadUsers() {
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
let users = try JSONDecoder().decode([User].self, from: data)await MainActor.run {
self.users = users
tableView.reloadData()
}
} catch {
print(error)
}
}
}
Теперь:
- сеть работает асинхронно
- декодирование происходит вне основного потока
- только обновление UI выполняется через MainActor
Простая схема
Плохой поток:
Main Thread ├── API Call ├── JSON Parsing ├── Data Processing └── UI Rendering (blocked)
Хороший:
Background Thread ├── API Call ├── JSON Parsing └── Data Processing Main Thread └── UI Update
2. Используйте Lazy Loading для дорогих объектов
Многие приложения инициализируют всё сразу.
Это увеличивает время запуска, потребление памяти и количество ненужной работы.
Пример:
class DashboardViewController: UIViewController {
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
}
Этот объект создаётся, даже если никогда не будет использован.
А теперь представьте:
- обработку изхображений
- кеширование
- парсеры
- большие manager-объекты
на множестве экранов.
Более правильный подход:
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
Теперь объект инициализируется только при необходимости.
Почему это важно
Большие приложения часто содержат множество view controller’ов, несколько сервисов, тяжёлые utility-объекты.
Ленивая загрузка уменьшает начальное давление на память, улучшает скорость запуска и уменьшает количество ненужных аллокаций.
3. Переиспользуйте DateFormatter и NumberFormatter
Создание formatter-ов — дорогая операция.
Очень частая ошибка:
func formattedDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}
Новый formatter создаётся каждый раз.
Внутри табличных представлений, коллекций или больших списков. Это очень быстро становится дорогой операцией.
Более правильный подход:
class FormatterManager {
static let sharedDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
}
Использование:
let text = FormatterManager.sharedDateFormatter.string(from: date)
Реальное влияние
Приложения, отображающие сообщения чатов, транзакции, ленты или уведомления, могут рендерить тысячи форматированных значений.
Снижение количества созданий formatter-ов улучшает скроллинг, использование CPU, производительность рендера.
4. Правильно оптимизируйте изображения
Изображения — одна из самых частых причин проблем с производительностью и памятью.
Частые ошибки:
- загрузка full-resolution изображений без необходимости
- декодирование изображений на main thread
- отсутствие кеширования
- большие изображения внутри scrollable списков
Плохой пример
imageView.image = UIImage(named: "large_photo")
Если изображение очень большое, загружается повторно или используется внутри collection view, потребление памяти резко возрастает.
Более правильный подход
Используйте:
- кеширование изображений
- resize
- асинхронную загрузку
Пример с уменьшением:
func downsample(imageAt imageURL: URL,
to pointSize: CGSize,
scale: CGFloat) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else {
return nil
}
let maxDimension = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimension
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
return UIImage(cgImage: cgImage)
}
Почему это важно
Большие изображения увеличивают давление на память, лаги при скролле, возрастает стоимость декодирования и вероятность крашей из-за ограничений памяти.
Это особенно часто встречается в социальных приложениях, e-commerce приложениях, приложениях с галереями и новостными лентами.
5. Уменьшайте лишнюю работу Auto Layout
Auto Layout — мощная система. Но очень сложные иерархии становятся дорогими.
Особенно внутри:
- переиспользуемых ячейках
- глубоко вложенных представлениях
- анимируемых макетах
Пример проблемы
UITableViewCell, содержащая несколько stack view, вложенные constraints, динамический resize, постоянно скрывающиеся/появляющиеся view, создаёт повторные макетные вычисления.
Пользователь ощущает пропуска кадров, лаги скролла, задержки рендера.
Более правильный подход
Упрощайте иерархии.
Вместо:
StackView ├── StackView │ ├── StackView │ │ ├── Label │ │ └── Image
Предпочитайте более плоские структуры, когда это возможно.
Дополнительная оптимизация
Избегайте постоянных вызовов:
layoutIfNeeded()
Внутри анимаций или операций скролла без необходимости.
Потому что принудительный перерасчёт layout-а — дорогая операция.
6. Избегайте больших View Controller’ов
Большие View Controller’ы косвенно ухудшают производительность.
Они часто содержат обращение к сети, бизнес-логику, парсинг, рендеринг, state management. И все в в одном месте.
Пример:
class HomeViewController: UIViewController {
func fetchData() {}
func parseResponse() {}
func saveCache() {}
func updateUI() {}
func handleAnalytics() {}
}
Со временем в рнем растёт количество ответственности, становится сложнее контролировать поток выполнения, во время рендера экрана yfxbyftn выполняться лишняя работа.
Более правильный подход
Разделяйте:
- сеть
- управление состояниями
- бизнес-логику
- рендеринг UI
Пример:
ViewController
└── ViewModel
└── Repository
└── API Layer
Это улучшает читаемость, тестируемость, контроль выполнения и отладку производительности.
7. Кешируйте умно вместо повторения
Многие приложения постоянно вызывают API, обрабатывают данные, декодируют ответы и генерируют дорогие UI-state’ы.
Это тратит батарею, сеть, циклы CPU.
Пример проблемы:
func loadProfile() async {
let profile = try await api.fetchProfile()
self.profile = profile
}
Каждое открытие экрана делает новый запрос.
Более правильный подход
Используйте кеширование.
class ProfileCache {
static var cachedProfile: User?
}
Затем:
if let cached = ProfileCache.cachedProfile {
self.profile = cached
} else {
let profile = try await api.fetchProfile()
ProfileCache.cachedProfile = profile
}
Реальное влияние
Кеширование улучшает скорость запуска, отзывчивость, работу без сети, энергоэффективность.
Но кеширование также требует управление сроком годности кеша, логикой аннулирования, управление согласованностью.
Зато качественное кэширование значительно улучшает пользовательский опыт.
8. Используйте частичные обновления вместо полной перезагрузки списка
Очень частая проблема:
tableView.reloadData()
Это перезагружает всё.
Для больших списков ячейки заново рендерятся без необходимости, layout пересчитывается, исчезают анимации, ухудшается скроллинг.
Более правильный подход
Используйте:
- Diffable Data Source
- пакетные обновления
- обновления элементов
Пример:
tableView.performBatchUpdates {
tableView.insertRows(at: [indexPath], with: .automatic)
}
Или современные diffable snapshots.
Почему это важно
Социальные приложения и ленты обновляются постоянно. Эффективные обновления уменьшают лишний рендеринг, работу layout системы и количество пропущенных кадров. И при этом улучшается воспринимаемое качество работы приложения.
9. Понимайте разницу между Value Types и Reference Types
struct и class в Swift ведут себя по-разному.
Понимание этого помогает с производительностью.
Структуры
struct User {
let name: String
}
Тип значения. Копируется при назначении.
Классы
class User {
var name: String
init(name: String) {
self.name = name
}
}
Ссылочный тип. Общая ссылка.
Почему это важно
Большое количество ненужных копирований структур может увеличивать потребление памяти.
Но для классов нужно управление reference’ами, retain cycle’ы, у них есть проблемы shared mutable state.
Оптимизация — это не:
Всегда используйте struct.
Это понимание компромиссов.
Пример
Большие вложенные struct-ы, которые постоянно обновляются внутри списков, могут становиться дорогими.
В то же время class-ы могут уменьшить копирование, но увеличить сложность.
Решения по производительности зависят от конкретных паттернов использования.
10. Регулярно используйте Instruments
Это одна из самых недооценённых техник оптимизации. Многие разработчики пытаются угадывать проблемы производительности. Это почти никогда не работает. Нужны измерения.
Важные Instruments
Time Profiler
Показывает:
- тяжелые операции
- медленные функции
- узкие места в рендеринге
Allocations
Показывает:
- рост памяти
- паттерны создания объектов
- лишние аллокации
Leaks
Находит:
- утечки памяти
- удерживаемые объекты
- проблемы жизненного цикла
Core Animation
Показывает:
- пропущенные кадры
- проблемы рендера
- узкие места в рендеринге UI
Реальный пример
Вы замечаете лаги при скролле.
Без Instruments:
- начинаются догадки
- происходят случайные оптимизации
С Instruments:
- обнаруживается пик в декодировании изображений
- выявляется блокировка основного потока
- точно видите узкое место
Это меняет всё.
Производительность — это обычно множество маленьких улучшений
Большинство приложений становятся медленными не из-за одной катастрофической ошибки.
Они замедляются из-за повторяющихся мелких неэффективностей, лишнего рендеринга, чрезмерного использования памяти, плохих решений с потоками и повторяющейся работы.
Оптимизация производительности обычно накопительная.
Маленькие улучшения в:
- рендеринге
- памяти
- сети
- макетах
- потоке исполнения
вместе создают заметно лучшее приложение.
Простое изменение в мышлении о производительности
Многие разработчики спрашивают:
«Работает ли эта фича?»
Опытные разработчики также спрашивают:
«Насколько дорогая эта фича?»
И это очень важный вопрос.
Потому что каждая фича добавляет потребление памяти, стоимость рендера, нагрузку на CPU и сложность в управлении состоянием.
Со временем эти затраты накапливаются.
Финальная мысль
Оптимизация производительности в Swift — это не про написание «умного» кода.
Это про уменьшение лишней работы, контроль потока выполнения, про аккуратное управление памятью, повышение эффективности рендера и
понимание того, что реально чувствует пользователь.
И самое важное.
Пользователи не понимают вашу архитектуру.
Они понимают отзывчивость, гладкость, скорость, время работы батареи и стабильность.
И очень часто улучшение производительности приложения связано не с добавлением новых технологий, а с удалением лишней работы.

