Хотя современное оборудование iOS является достаточно мощным для решения многих интенсивных и сложных задач, устройство все еще может чувствовать себя безответным, если вы не будете осторожны, как ваше приложение выполняет. В этой статье мы рассмотрим пять трюков оптимизации, которые сделают ваше приложение более отзывчивым.
1. Размытая многоразовая ячейка
Вы, наверное, использовали tableView.dequeueReusableCell(withIdentifier:for:)
внутри tableView(_:cellForRowAt:)
раньше. Всегда интересовали почему вы должны последовать за этим несуразняемым API, вместо как раз проходить блок клетки в? Давайте рассмотрим рассуждения об этом.
Допустим, у вас есть представление таблицы с тысячей строк. Без использования многоразовых ячеек нам пришлось бы создать новую ячейку для каждой строки, как это:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create a new cell whenever cellForRowAt is called.
let cell = UITableViewCell()
cell.textLabel?.text = "Cell (indexPath.row)"
return cell
}
Как вы могли подумать, это добавит тысячу ячеек в память устройства при прокрутке на дно. Представьте себе, что произойдет, если каждая ячейка содержит и UIImageView
много текста: Загрузка их все сразу может привести к приложению иссякнут памяти! Кроме того, каждая ячейка потребует выделения новой памяти во время прокрутки. Если вы прокрутите представление таблицы быстро, много небольших кусков памяти будет выделено на лету, и этот процесс сделает UI janky!
Чтобы решить эту проблему, Apple предоставила нам dequeueReusableCell(withIdentifier:for:)
метод. Повторное использование ячеек работает, поместив ячейку, которая больше не видна на экране, в очередь, и когда новая ячейка будет видна на экране (скажем, последующая ячейка ниже, когда пользователь прокручивает вниз), представление таблицы извлекет ячейку из этой очереди и изменит ее в cellForRowAt indexPath:
метод.
Используя очередь для хранения ячеек, представление таблицы не требует создания тысячи ячеек. Вместо этого ему необходимо достаточно ячеек, чтобы покрыть область представления таблицы.
С помощью dequeueReusableCell
, мы можем уменьшить память, используемую приложением и сделать его менее склонны к запуску памяти!
2. Использование стартового экрана, который выглядит как начальный экран
Как уже упоминалось в Руководящих принципах apple Human Interface (HIG), экраны запуска могут быть использованы для повышения восприятия отзывчивости приложения:
«Это исключительно предназначено для повышения восприятия вашего приложения, как быстро, чтобы запустить и немедленно готов к использованию. Каждое приложение должно предоставить экран запуска».
Это распространенная ошибка использовать экран запуска в качестве экрана всплеска, чтобы показать брендинг или добавить анимацию загрузки. Дизайн экрана запуска, чтобы быть идентичным первому экрану вашего приложения, как упоминалось Apple:
«Дизайн экрана запуска, который почти идентичен первому экрану вашего приложения. Если вы включаете элементы, которые выглядят по-разному, когда приложение заканчивает запуск, люди могут испытывать неприятную вспышку между экраном запуска и первым экраном приложения.
«Запуск экрана не является возможностью брендинга. Не проектировать запись опыт, который выглядит как всплеск экрана или «О» окно. Не включайте логотипы или другие элементы брендинга, если они не являются статической частью первого экрана приложения.
Использование стартового экрана для целей загрузки или брендинга может замедлить время первого использования и заставить пользователя почувствовать, что приложение является вялым.
При запуске нового проекта iOS LaunchScreen.storyboard
будет создан пробел. Этот экран будет показан пользователю во время загрузки приложения контроллеров представления и макета.
Чтобы приложение чувствовало себя быстрее, можно спроектировать экран запуска, чтобы быть похожим на первый экран (контроллер просмотра), который будет показан пользователю.
Например, экран запуска приложения Safari похож на его первый вид:
Раскадровка экрана запуска похожа на любой другой файл раскадровки, за исключением того, что вы можете использовать только стандартные классы UIKit, такие как UIViewController, UITabBarController и UINavigationController. Если вы попытаетесь использовать другие пользовательские подклассы (например, UserViewController), Xcode уведомит вас о том, что использование имен пользовательских классов запрещено.
Еще одна вещь, чтобы отметить, что UIActivityIndicatorView
не анимировать при размещении на экране запуска, потому что ioOS будет генерировать статическое изображение с экрана экрана экрана раскадровки и отображает его для пользователя. (Это кратко упоминается в презентации WWDC 2014 «Платформы государство Союза«, вокруг 01:21:56
.)
HIG от Apple также советует нам не включать текст на нашем экране запуска, потому что экран запуска статический, и вы не можете локализовать текст для удовлетворения различных языков.
Рекомендуемое чтение: Мобильное приложение с функцией распознавания лиц: Как сделать его реальным
3. Восстановление состояния для контроллеров представления
Сохранение и восстановление состояния позволяют пользователю вернуться в точно такое же состояние пользовательского интерфейса непосредственно перед тем, как они покинули приложение. Иногда из-за недостаточной памяти операционной системе может потребоваться удалить приложение из памяти, пока приложение находится в фоновом режиме, и приложение может потерять свой последний режим пользовательского времени, если оно не будет сохранено, что может привести к потере пользователями своей работы!
На экране многозадачности мы можем увидеть список приложений, которые были помещены в фоновом режиме. Мы можем предположить, что эти приложения все еще работают в фоновом режиме; в действительности, некоторые из этих приложений могут быть убиты и перезапущены системой из-за требований памяти. Снимки приложения, которые мы видим в представлении многозадачности, на самом деле являются скриншотами, сделанными системой справа, когда мы вышли из приложения (т.е. для того, чтобы перейти к дому или на экране многозадачности).
iOS использует эти скриншоты, чтобы создать иллюзию того, что приложение все еще работает или все еще отображает это конкретное представление, в то время как приложение, возможно, уже было прекращено или перезапущено в фоновом режиме, по-прежнему отображая тот же скриншот.
Вы когда-нибудь испытывали, после возобновления приложения с экрана многозадачности, что приложение показывает пользовательский интерфейс отличается от снимка, показанного в многозадачном представлении? Это связано с тем, что приложение не реализовало механизм восстановления состояния, а отображаемые данные были потеряны, когда приложение было убито в фоновом режиме. Это может привести к плохому опыту, поскольку пользователь ожидает, что ваше приложение будет в том же состоянии, что и при его походе.
Из статьиApple :
«Они ожидают, что ваше приложение будет в том же состоянии, когда они покинули его. Сохранение и восстановление состояния гарантирует, что ваше приложение вернется в прежнее состояние, когда оно запускается снова.»
UIKit делает большую работу по упрощению сохранения состояния и восстановления для нас: он обрабатывает сохранение и загрузку состояния приложения автоматически в соответствующее время. Все, что нам нужно сделать, это добавить некоторую конфигурацию, чтобы сказать приложение для поддержки сохранения состояния и восстановления и сказать приложение, какие данные должны быть сохранены.
Для обеспечения экономии и восстановления состояния мы можем реализовать эти два метода AppDelegate.swift
в:
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
Это позволит приложению сохранить и восстановить состояние приложения автоматически.
Далее мы сообщим приложению, какие контроллеры представления должны быть сохранены. Мы делаем это, указывая «Идентификатор восстановления» в раскадровке:
Вы также можете проверить «Использовать идентификатор раскадровки», чтобы использовать идентификатор раскадровки в качестве идентификатора восстановления.
Чтобы установить идентификатор восстановления в коде, мы можем использовать restorationIdentifier
свойство контроллера представления.
// ViewController.swift
self.restorationIdentifier = "MainVC"
Во время сохранения состояния любой контроллер представления или представление, назначенное идентификатором восстановления, будет сохранять сядёт в состояние диска.
Идентификаторы восстановления могут быть сгруппированы для формирования пути восстановления. Идентификаторы сгруппированы с использованием иерархии представления, от контроллера корневого представления до текущего активного контроллера представления. Предположим, что MyViewController встроен в навигационный контроллер, который встроен в другой контроллер панели вкладок. Предполагая, что они используют свои собственные названия классов в качестве идентификаторов восстановления, путь восстановления будет выглядеть следующим образом:
TabBarController/NavigationController/MyViewController
Когда пользователь покидает приложение, а MyViewController является активным контроллером представления, этот путь будет сохранен приложением; после этого app вспомнит предыдущую иерархию представления показанную(контроллер панели Tab — контроллер навигации — Мой регулятор представления).
После назначения идентификатора восстановления нам необходимо реализовать кодовое restorableState (с кодером:) и расшифровыватьВосстановительогосударство (с кодером:) методы для каждого из сохраненных контроллеров представления. Эти два метода позволяют указать, какие данные необходимо сохранить или загрузить и как их кодировать или декодировать.
Давайте посмотрим контроллер представления:
// MyViewController.swift
// MARK: State restoration
// UIViewController already conforms to UIStateRestoring protocol by default
extension MyViewController {
// will be called during state preservation
override func encodeRestorableState(with coder: NSCoder) {
// encode the data you want to save during state preservation
coder.encode(self.username, forKey: "username")
super.encodeRestorableState(with: coder)
}
// will be called during state restoration
override func decodeRestorableState(with coder: NSCoder) {
// decode the data saved and load it during state restoration
if let restoredUsername = coder.decodeObject(forKey: "username") as? String {
self.username = restoredUsername
}
super.decodeRestorableState(with: coder)
}
}
Не забудьте назвать реализацию суперкласса в нижней части вашего собственного метода. Это гарантирует, что родительский класс имеет возможность сохранить и восстановить состояние.
После того, как объекты закончили расшифровку, applicationFinishedRestoringState()
будет вызван, чтобы сообщить контроллеру представления, что состояние восстановлено. Мы можем обновить пульт связи для контроллера представления в этом методе.
// MyViewController.swift
// MARK: State restoration
// UIViewController already conforms to UIStateRestoring protocol by default
extension MyViewController {
...
override func applicationFinishedRestoringState() {
// update the UI here
self.usernameLabel.text = self.username
}
}
Вот оно! Это основные методы для реализации сохранения состояния и восстановления для вашего приложения. Имейте в виду, что операционная система удалит сохраненное состояние, когда приложение закрывается пользователем, чтобы не застрять в нарушенном состоянии в случае что-то идет не так в государственной консервации и реставрации.
Кроме того, не храните в состоянии данные о модели (т.е. данные, которые должны были быть сохранены для UserDefaults или Core Data), даже если это может показаться удобным. Данные состояния будут удалены, когда пользователь покидает ваше приложение, и вы, конечно, не хотите потерять данные модели таким образом.
Чтобы проверить, хорошо ли работают сохранение и восстановление состояния, выполните ниже:
- Создайте и запустите приложение с помощью Xcode.
- Перейдите к экрану с сохранением состояния и восстановления, что вы хотите проверить.
- Вернуться на домашний экран (путем салфетки или двойное нажатие кнопки домой, или нажатием кнопки в Shift ⇧ Cmd ⌘ H симуляторе), чтобы отправить приложение на задний план.
- Остановите приложение в Xcode, нажав кнопку ⏹.
- Запустите приложение снова и проверьте, было ли успешно восстановлено состояние.
Поскольку этот раздел охватывает только основы государственного сохранения и восстановления, я рекомендую следующие статьи Apple Inc. для более глубоких знаний о восстановлении государства:
4. Сократить использование непрозрачных представлений как можно больше
Непрозрачное представление — это представление, которое не имеет прозрачности, а это означает, что любой элемент uI, расположенный позади него, вообще не виден. Мы можем установить представление, чтобы быть непрозрачным в интерфейсе Builder:
Или мы можем сделать это программно с isOpaque
свойством UIView:
view.isOpaque = true
Установка представления на непрозрачную сделает систему рисования оптимизировать некоторую производительность рисования при рендеринге экрана.
Если представление имеет прозрачность (т.е. альфа ниже 1,0), то iOS придется проделать дополнительную работу, чтобы вычислить, что должно отображаться путем смешивания различных уровней представлений в иерархии представления. С другой стороны, если представление настроено на непрозрачный, то система чертежа просто поставит этот вид вперед и избежит дополнительной работы по смешиванию нескольких слоев представления позади него.
Вы можете проверить, какие слои смешиваются (непрозрачные) в симуляторе iOS, проверяя Debug и Цветные смешанные слои.
Проверив опцию Color Blended Layers, можно увидеть, что некоторые виды красные, а некоторые – зеленые. Красный цвет указывает на то, что представление не является непрозрачным и что его выходное отображение является результатом слитых за ним слоев. Зеленый цвет указывает на то, что представление непрозрачно и никакого смешивания не было сделано.
Приведенные выше метки («View Friends» и т.д.) выделены красным цветом, поскольку, когда метка перетаскивается на раскадровку, ее цвет фона по умолчанию становится прозрачным. Когда система чертежа составляет дисплей вблизи области метки, она будет просить слой за этикеткой и сделать некоторые расчеты.
Одним из способов оптимизации производительности приложения является сокращение количества представлений, выделенных красным цветом.
Изменяя label.backgroundColor = UIColor.clear
к , мы можем уменьшить слой label.backgroundColor = UIColor.white
смешивания между этикеткой и слой представления позади него.
Возможно, вы заметили, что, даже если вы установили UIImageView на непрозрачный и присвоил ему цвет фона, симулятор будет по-прежнему отображать красный цвет в представлении изображения. Вероятно, это связано с тем, что изображение, используемое для представления изображения, имеет альфа-канал.
Чтобы удалить альфа-канал для изображения, вы можете использовать приложение Preview, чтобы сделать дубликат изображения ( q Shift ⇧ Cmd ⌘ ), и снять S «Альфа» флажок при сохранении.
5. Передайте функции тяжелой обработки фоновым потокам (GCD)
Поскольку UIKit работает только на основной поток, выполнение тяжелой обработки на основной потоке замедлит работу uI. Основной поток используется UIKit не только для обработки и реагирования на пользовательский вход, а также для рисования экрана.
Ключ к тому, чтобы приложение реагировать, заключается в том, чтобы переместить как можно больше задач обработки в фоновые потоки. Избегайте выполнения сложных вычислений, сетей и тяжелой операции io-нависла (например, чтение и запись на диск) на основном потоке.
Возможно, вы когда-то использовали приложение, которое вдруг стало не реагировать на сенсорный вход, и он чувствует, как приложение зависло. Это, скорее всего, вызвано приложением работает тяжелые задачи вычислений на основной поток.
Основной поток обычно чередуется между задачами UIKit (например, обработка пользовательского ввода) и некоторыми световыми задачами небольшими интервалами. Если тяжелая задача выполняется на основной поток, то UIKit придется подождать, пока тяжелая задача будет закончена, прежде чем сможет обрабатывать сенсорный вход.
По умолчанию на главном потоке выполняются методы жизненного цикла контроллера просмотра (например, viewDidLoad) и функции IBOutlet. Для перемещения задач обработки тяжелых данных в фоновый поток можно использовать очереди Grand Central Dispatch, предоставляемые Apple.
Вот шаблон для переключения очередей:
// Switch to background thread to perform heavy task.
DispatchQueue.global(qos: .default).async {
// Perform heavy task here.
// Switch back to main thread to perform UI-related task.
DispatchQueue.main.async {
// Update UI.
}
}
Стенды qos
для «качества обслуживания». Различные значения качества обслуживания указывают на различные приоритеты для указанных задач. Операционная система будет выделять больше времени процессора и пропускной способностью ввода-навистого процессора для задач, выделенных в очередях с более высокими значениями зоОС, а это означает, что задача будет выполняться быстрее в очереди с более высокими значениями qoS. Более высокое значение ЗоС также будет потреблять больше энергии из-за этого, используя больше ресурсов.
Вот список значений ЗОС от самого высокого к самому низкому приоритету:
Apple предоставила удобную таблицу с примерами, которые значения zoS для использования для различных задач.
Следует иметь в виду, что весь код UIKit всегда должен выполняться на основном потоке. Изменение объектов UIKit UILabel
(например, UIImageView
и) на фоновом потоке может иметь непредвиденные последствия, такие как uI, фактически не обновляющийся, сбой и так далее.
Из статьиApple :
«Обновление uI на потоке, кроме основного потока, является распространенной ошибкой, которая может привести к пропущенным обновлениям uI, визуальным дефектам, порочинительным данным и сбоям».
Я рекомендую смотреть видео Apple WWDC 2012 на uI-валюте, чтобы лучше понять, как создать отзывчивое приложение.
Заметки
Компромисс оптимизации производительности заключается в том, что вам нужно написать больше кода или настроить дополнительные настройки поверх функциональности приложения. Это может привести к тому, что ваше приложение будет доставлено позже, чем ожидалось, и в будущем у вас будет больше кода для обслуживания, а больше кода означает потенциально больше ошибок.
Прежде чем тратить время на оптимизацию приложения, спросите себя, является ли приложение уже гладким или есть ли у него какая-то безответная часть, которая действительно нуждается в оптимизации. Тратить много времени на оптимизацию и без того гладкого приложения для сбривания 0,01 секунды может оказаться не целесообразным, так как время можно было бы лучше потратить на разработку лучших функций или других приоритетов.
Дополнительные ресурсы
- «Сюита вкусных конфет для глаз iOS,»Тим Оливер, Tokyo iOS Meetup 2018 (видео)
- «Созданиеодновременных пользовательских интерфейсов на iOS»,Энди Матущак, WWDC 2012 (Видео)
- «Сохранениеui-ui вашего приложения череззапуски,» Apple
- «Руководствопо программированию параллелизма: Отправка Очередей,»Архивдокументации, Apple
- «Главнаянить Checker,»Apple
Источник: smashingmagazine.com