Глобальные функции (их можно вызвать отовсюду без привязки к области действия определённого типа) — это довольно старая концепция, которая была популярна в таких языках, как С и Objective-C. А вот в Swift её использовать не рекомендуется, тут лучше подходят аккуратно типизированные и разграниченные вещи типа Swifty.
Так исторически сложилось, что в стандартной библиотеке Swift достаточно публичных глобальных функций. Некоторые из них даже дожили до сегодняшнего дня и успешно применяются в работе. Посмотрим, что из себя представляют функции zip
и dump
.
Возможно, это самая известная глобальная функция. Она нужна, чтобы объединять два и более массива в единую последовательность кортежа. Такое очень сильно помогает, когда нужно проитерировать два объекта одновременно. Без zip
надо было бы вручную создать цикл for
и отдельно получить каждый индекс каждого массива. А с zip
можно получить элементы из всех массивов самым удобным способом for-in
.
Например, есть экранная форма регистрации пользователя и мы хотим обновить текстовые поля, чтобы показать список результатов валидации, полученных из бэкенда. Пишем:
func present(validationResults: [FieldValidationResult], inTextFields textFields: [MyTextField]) { for i in 0..<textFields.count { let field = textFields[i] let result = validationResults[i] field.render(validationResult: result) }}А с функцией zip
можем убрать всё ручное индексирование:
Для возврата типа функции zip
есть объект Zip2Sequence
, который соответствует Sequence
. Так что все другие методы, связанные с последовательностью, можно применить и тут, включая её трансформацию в массив.
Функция dump
— это изящная альтернатива выводу объектов. В то время как вывод объектов — чистый синтаксический “сахар” для свойств типа description
или debugDescription
, то функция dump
— суперзаряженная версия Mirror(reflecting:)
. Она выводит содержимое объекта при помощи рефлексии, что часто даёт результат со значительно большей информацией, в т.ч. иерархию объектов.
class Foo: NSObject {
let bar: String = "bar"
}
let foo = Foo()
print(foo)
// <SwiftRocks.Foo: 0x1030b9250>
dump(foo)
// ▿ <SwiftRocks.Foo: 0x1030b9250> #0
// - super: NSObject
// - bar: "bar"
Глобальная функция sequence()
в целом немного непонятная, но при этом классная. С ней можно писать рекурсивные функции с более приятным синтаксисом.
Давайте представим, что мы меняем цвет фона в представлении и во всём остальном, что ниже по иерархии. Возможно, вам на ум приходит такой вот цикл while
:
Это лучший юзкейс для sequence()
. Формат такой: задача этой функции дать вам Sequence
, которая обращается к определённому замыканию снова и снова. Так как рекурсивная часть этого метода всегда одинаковая (currentView = currentView?.superview
), то мы можем применять sequence()
. Так функция трансформируется в простой цикл for
:
Этот код работает таким образом, что sequence()
возвращает пользовательский тип UnfoldFirstSequence
. Это простой враппер для Sequence
, который продолжает применять замыкание снова и снова в его функции next()
.
Функция isKnownUniquelyReferenced
получает объект class
и возвращает логическое значение, которое указывает, что на объект ссылаются только один раз. Это нужно, чтобы вы смогли реализовать семантику значений для ссылочных типов. И хотя структуры сами по себе являются типами значений, их содержимое может не быть таковым. Запомните, что перемещение класса внутрь структуры не означает, что он будет скопирован по назначению:
class Foo: NSObject {
var bar: String = "bar"
}
struct FooHolder {
let foo: Foo = Foo()
var intValue: Int = 1
}
var fooHolder = FooHolder()
var fooHolder2 = fooHolder
fooHolder2.foo.bar = "bar2"
fooHolder2.intValue = 2
print(fooHolder.intValue)
// 1
print(fooHolder2.intValue)
// 2
print(fooHolder.foo.bar)
// bar2
print(fooHolder2.foo.bar)
// bar2
Разберём пример выше: хотя fooHolder2
и его основное число — это отдельные сущности по отношению к первоначальному холдеру, основной класс всё ещё связан с ними обоими. Чтобы это решить, можно применить isKnownUniquelyReferenced
. Эта функция поможет определить, когда происходит обращение к этому свойству, и при необходимости создать новый инстанс класса:
Интересно, что стандартная библиотека Swift позволяет так использовать семантику копирования при записи по отношению к массивам и строчкам.
Функция repeatElement()
делает именно то, о чём говорит её название. Подав на вход объект и число, мы получим Sequence
из объектов данного типа, которая может итерироваться. Количество объектов в последовательности равно данному числу.
Повторение элементов — распространённая операция в Swift, особенно для заполнения пробелов в строчках и массивах. Фактически, у большинства этих типов даже есть специальный инициализатор для этого:
let array = [Int](repeating: 0, count: 10)Так почему вам стоит использовать repeatElement
? Причина в производительности. repeatElement()
возвращает тип Repeated<T>
Sequence
, похожий на Zip2Sequence
в том, что он ничего не делает, кроме того, что обеспечивает повторяющуюся функциональность. Давайте представим, что вы хотите заменить определённую секцию числового массива другим числом. Один способ для этого — применить replaceSubrange
с другим массивом:
Да, это срабатывает, но применение [Int](repeating:)
приносит и дополнительные заботы: нужно инициализировать буферные массивы, которые тут без надобности. Если вам нужна только повторяющаяся функциональность, то пользуйтесь repeatElement
. Это будет эффективным решением.
Есть ещё кое-что довольно популярное — это функция stride()
. Она появилась в арсенале Swift как способ создавать циклы for
, которые могут пропускать определённые элементы. Она была добавлена потому, что исчез способ, эквивалентный стилю С.
Теперь применим 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
для содержания значения.
Итак, очевидно, что никакой из этих методов не является обязательным, но с их помощью можно написать легко поддерживаемый код с лучшей производительностью, чего нельзя добиться, используя устаревшие решения.
Перевод статьи Bruno Rocha: Useful Global Swift Functions
Комментарии