Найти в Дзене
Nuances of programming

Полезные глобальные функции языка Swift

Оглавление

Источник: Nuances of Programming

Глобальные функции (их можно вызвать отовсюду без привязки к области действия определённого типа) — это довольно старая концепция, которая была популярна в таких языках, как С и Objective-C. А вот в Swift её использовать не рекомендуется, тут лучше подходят аккуратно типизированные и разграниченные вещи типа Swifty.

Так исторически сложилось, что в стандартной библиотеке Swift достаточно публичных глобальных функций. Некоторые из них даже дожили до сегодняшнего дня и успешно применяются в работе. Посмотрим, что из себя представляют функции zip и dump.

zip()

Возможно, это самая известная глобальная функция. Она нужна, чтобы объединять два и более массива в единую последовательность кортежа. Такое очень сильно помогает, когда нужно проитерировать два объекта одновременно. Без zip надо было бы вручную создать цикл for и отдельно получить каждый индекс каждого массива. А с zip можно получить элементы из всех массивов самым удобным способом for-in.

Например, есть экранная форма регистрации пользователя и мы хотим обновить текстовые поля, чтобы показать список результатов валидации, полученных из бэкенда. Пишем:

А с функцией zip можем убрать всё ручное индексирование:

Для возврата типа функции zip есть объект Zip2Sequence, который соответствует Sequence. Так что все другие методы, связанные с последовательностью, можно применить и тут, включая её трансформацию в массив.

dump()

Функция dump — это изящная альтернатива выводу объектов. В то время как вывод объектов — чистый синтаксический “сахар” для свойств типа description или debugDescription, то функция dump — суперзаряженная версия Mirror(reflecting:). Она выводит содержимое объекта при помощи рефлексии, что часто даёт результат со значительно большей информацией, в т.ч. иерархию объектов.

sequence()

Глобальная функция sequence() в целом немного непонятная, но при этом классная. С ней можно писать рекурсивные функции с более приятным синтаксисом.

Давайте представим, что мы меняем цвет фона в представлении и во всём остальном, что ниже по иерархии. Возможно, вам на ум приходит такой вот цикл while:

Это лучший юзкейс для sequence(). Формат такой: задача этой функции дать вам Sequence, которая обращается к определённому замыканию снова и снова. Так как рекурсивная часть этого метода всегда одинаковая (currentView = currentView?.superview), то мы можем применять sequence(). Так функция трансформируется в простой цикл for:

Этот код работает таким образом, что sequence() возвращает пользовательский тип UnfoldFirstSequence. Это простой враппер для Sequence, который продолжает применять замыкание снова и снова в его функции next().

isKnownUniquelyReferenced()

Функция isKnownUniquelyReferenced получает объект class и возвращает логическое значение, которое указывает, что на объект ссылаются только один раз. Это нужно, чтобы вы смогли реализовать семантику значений для ссылочных типов. И хотя структуры сами по себе являются типами значений, их содержимое может не быть таковым. Запомните, что перемещение класса внутрь структуры не означает, что он будет скопирован по назначению: 

Разберём пример выше: хотя fooHolder2 и его основное число — это отдельные сущности по отношению к первоначальному холдеру, основной класс всё ещё связан с ними обоими. Чтобы это решить, можно применить isKnownUniquelyReferenced. Эта функция поможет определить, когда происходит обращение к этому свойству, и при необходимости создать новый инстанс класса:

Интересно, что стандартная библиотека Swift позволяет так использовать семантику копирования при записи по отношению к массивам и строчкам. 

repeatElement()

Функция repeatElement() делает именно то, о чём говорит её название. Подав на вход объект и число, мы получим Sequence из объектов данного типа, которая может итерироваться. Количество объектов в последовательности равно данному числу.

Повторение элементов — распространённая операция в Swift, особенно для заполнения пробелов в строчках и массивах. Фактически, у большинства этих типов даже есть специальный инициализатор для этого:

let array = [Int](repeating: 0, count: 10)

Так почему вам стоит использовать repeatElement? Причина в производительности. repeatElement() возвращает тип Repeated<T> Sequence, похожий на Zip2Sequence в том, что он ничего не делает, кроме того, что обеспечивает повторяющуюся функциональность. Давайте представим, что вы хотите заменить определённую секцию числового массива другим числом. Один способ для этого — применить replaceSubrange с другим массивом:

array.replaceSubrange(2...7, with: [Int](repeating: 1, count: 6))
print(array)
// [0, 0, 1, 1, 1, 1, 1, 1, 0, 0]

Да, это срабатывает, но применение [Int](repeating:) приносит и дополнительные заботы: нужно инициализировать буферные массивы, которые тут без надобности. Если вам нужна только повторяющаяся функциональность, то пользуйтесь repeatElement. Это будет эффективным решением. 

array.replaceSubrange(2...7, with: repeatElement(1, count: 6))

stride()

Есть ещё кое-что довольно популярное — это функция stride(). Она появилась в арсенале Swift как способ создавать циклы for, которые могут пропускать определённые элементы. Она была добавлена потому, что исчез способ, эквивалентный стилю С. 

for (int i = 0; i < 10; i += 2) { ... }

Теперь применим stride(), чтобы получить аналогичное поведение. 

for i in stride(from: 0, to: 10, by: 2) {
// от 0 до 9, пропустить нечетные числа.
}

Аргументы этой функции соответствуют протоколу Strideable, в котором представлены объекты, отражающие концепцию смещений. 

Привожу пример того, как мы можем добавить концепцию разницы между двумя днями в объектах Date так, чтобы их можно было применять вместе со stride():

Помните, что у Date уже есть реализация методов Strideable, которая срабатывает за секунды. Так что копирование её в проект не сработает. 

Другие полезные математические функции

Математические функции

max()— возвращает максимальное значение аргументов;

min()— возвращает минимальное значение аргументов;

abs()— возвращает абсолютное значение аргумента (полезно в вопросах так называемого спортивного программирования).

Значения

swap()— меняет значение двух объектов. Я не упоминал об этой функции выше, потому что, если вам нужно поменять местами элементы массива, корректно будет пользоваться Array.swapAt(). Вы, конечно, можете применять swap() в других ситуациях, когда вам понадобится создать фейковое свойство aux для содержания значения.  

Заключение

Итак, очевидно, что никакой из этих методов не является обязательным, но с их помощью можно написать легко поддерживаемый код с лучшей производительностью, чего нельзя добиться, используя устаревшие решения. 

Читайте также:

Читайте нас в телеграмме и vk

Перевод статьи Bruno Rocha: Useful Global Swift Functions