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


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

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

zip()

Возможно, это самая известная глобальная функция. Она нужна, чтобы объединять два и более массива в единую последовательность кортежа. Такое очень сильно помогает, когда нужно проитерировать два объекта одновременно. Без 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 можем убрать всё ручное индексирование:

func present(validationResults: [FieldValidationResult], inTextFields textFields: [MyTextField]) { for (field, result) in zip(textFields, validationResults) { field.render(validationResult: result) }}

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

dump()

Функция 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()

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

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

var currentView: UIView? = selfwhile currentView != nil { currentView?.backgroundColor = .green currentView = currentView?.superview}

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

for view in sequence(first: self, next: { $0.superview } ) { view.backgroundColor = .green}

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

isKnownUniquelyReferenced()

Функция 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. Эта функция поможет определить, когда происходит обращение к этому свойству, и при необходимости создать новый инстанс класса:

struct FooHolder { private var _foo: Foo = Foo() var foo: Foo { mutating get { if isKnownUniquelyReferenced(&_foo) { return _foo } else { let newFoo = Foo() newFoo.bar = _foo.bar _foo = newFoo return _foo } } set { _foo = newValue } } var intValue: Int = 1}

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

repeatElement()

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

let repeated: Repeated<String> = repeatElement("SwiftRocks", count: 3)for value in repeated { print(value)}//SwiftRocks//SwiftRocks//SwiftRocks

Повторение элементов — распространённая операция в 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():

extension Date: Strideable { func advanced(by n: Int) -> Date { return Calendar.current.date(byAdding: .day, value: n, to: self)! } func distance(to other: Date) -> Int { return Calendar.current.dateComponents([.day], from: other, to: self).day! }}let startDate = Date()let finalDate = startDate.advanced(by: 5)for date in stride(from: startDate, to: finalDate, by: 1) { print(date)}// March 24th// March 25th// March 26th// March 27th// March 28th

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

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

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

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

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

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

Значения

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

Заключение

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


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


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.