Книга: Swift. Основы разработки приложений под iOS, iPadOS и macOS. 5-е изд. дополненное и переработанное
Назад: 17. Дополнительные возможности
Дальше: Часть V. Введение в разработку приложений

18. Ленивые вычисления

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

18.1. Понятие ленивых вычислений

«Ленивый» в Swift звучит как lazy. Можно сказать, что lazy — синоним производительности. Хорошо оптимизированные программы практически всегда используют ленивые вычисления. Возможно, вы работали с ними и в других языках. В любом случае, внимательно изу­чите ­приведенный далее материал.

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

Существует два типа ленивых элементов:

• lazy-by-name — значение элемента вычисляется при каждом доступе к нему;

• lazy-by-need — элемент вычисляется один раз при первом обращении к нему, после чего фиксируется и больше не изменяется.

Swift позволяет работать с обоими типами ленивых элементов, но в строгом соответствии с правилами.

18.2. Замыкания в ленивых вычислениях

С помощью замыканий мы можем создавать ленивые конструкции типа lazy-by-name, значение которых высчитывается при каждом обращении к ним.

Рассмотрим пример из листинга 18.1.

Листинг 18.1

var arrayOfNames = ["Helga", "Bazil", "Alex"]

print(arrayOfNames.count)

let nextName = { arrayOfNames.remove(at: 0) }

arrayOfNames.count // 3

nextName()

arrayOfNames.count // 2

В константе nextName хранится замыкание, удаляющее первый элемент массива arrayOfNames. Несмотря на то что константа объявлена, а ее значение проинициализировано, количество элементов массива не уменьшается до тех пор, пока не произойдет обращение к хранящемуся в ней замыканию.

Если пойти дальше, то можно сказать, что любая функция или метод являются lazy-by-name, так как их значение высчитывается при каждом обращении.

18.3. Свойство lazy

Некоторые конструкции языка Swift (например, массивы и словари) имеют свойство lazy, позволяющее преобразовать их в ленивые. Наиболее часто это происходит, когда существуют цепочки вызова свойств или методов и выделение памяти и вычисление промежуточных значений является бесполезной тратой ресурсов, так как эти значения никогда не будут использованы.

Рассмотрим следующий пример: существует массив целых чисел, значения которого непосредственно не используются в работе программы. Вам требуется лишь результат его обработки методом map(_:), и то не в данный момент, а позже (листинг 18.2).

Листинг 18.2

var baseCollection = [1,2,3,4,5,6]

var myLazyCollection = baseCollection.lazy

type(of:myLazyCollection) // LazyCollection<Array<Int>>.Type

var collection = myLazyCollection.map{$0 + 1}

type(of:collection) // LazyMapCollection<Array<Int>, Int>.Type

В результате выполнения возвращается ленивая коллекция. При этом память под отдельный массив целочисленных значений не выделяется, а вычисления метода map(_:) не производятся до тех пор, пока не произойдет обращение к переменной collection.

Вся прелесть такого подхода в том, что вы можете увеличивать цепочки вызовов, но при этом лишнего расхода ресурсов не будет (лис­тинг 18.3).

Листинг 18.3

var resultCollection = [1,2,3,4,5,6].lazy.map{$0 + 1}.filter{$0 % 2

                     == 0}

Array(resultCollection) // [2, 4, 6]

Назад: 17. Дополнительные возможности
Дальше: Часть V. Введение в разработку приложений