Можно ли применять вложенные циклы и как оптимизировать алгоритмы
Что проверяют
Понимаете ли вы, что дорого не само вложение циклов, а сложность и работа внутри итерации. Для iOS добавляется своя специфика: копирование значимых типов, мост в Objective-C и то, на каком потоке этот цикл крутится.
Как отвечать
Вложенные циклы нормальны там, где данные действительно двумерные: сетка, матрица, секции и строки таблицы. Проблема начинается, когда внутренний цикл занят поиском: для каждого элемента одного массива перебирается второй, и выходит O(n^2).
Типовое лечение это замена поиска на множество или словарь:
let known = Set(remote.map(\.id))
let fresh = local.filter { !known.contains($0.id) }Построение множества это один проход, проверка в среднем константа, итог линейный. Тот же приём закрывает поиск пар, группировку и сверку двух списков.
Что ещё стоит назвать:
- вынести из внутреннего цикла всё, что не зависит от его переменной: форматтеры, регулярные выражения, обращения к настройкам;
reserveCapacityперед наполнением массива, чтобы не платить за перевыделения;- ранний выход через
first(where:),containsилиbreakвместо полного прохода; - если данных много, увести работу с главного потока, иначе выигрыш в алгоритме не спасёт отзывчивость интерфейса.
Где спотыкаются
Выдают array.contains(element) за оптимизацию. На массиве это линейный поиск, и внутри цикла он даёт ту же квадратичную сложность. Ускоряет переход к Set или Dictionary, а не смена синтаксиса на более короткий.
Второе: забывают про семантику значений. firstIndex(of:) по массиву структур внутри цикла, накопление через += или изменение коллекции, на которую есть вторая ссылка, добавляют столько копирований, что профайлер показывает не ваш алгоритм, а работу с памятью.
Третье: оптимизируют вслепую. Пока цикл не показан в Instruments как горячий, переписывание его на хитрый алгоритм чаще всего меняет читаемый код на нечитаемый без выигрыша.