Используем замыкания в Swift по полной

Несмотря на то, что в Objective-C 2.0 присутствуют замыкания (известные как блоки), ранее эппловский API использовал их неохотно. Возможно, отчасти поэтому многие программисты под iOS с удовольствием эксплуатировали сторонние библиотеки, вроде AFNetworking, где блоки применяются повсеместно. С выходом Swift, а также добавлением новой функциональности в API, работать с замыканиями стало чрезвычайно удобно. Давайте рассмотрим, какими особенностями обладает их синтаксис в Swift, и какие трюки можно с ними «вытворять».ad8b57b045ba43be81bf5dfd2805ef05.jpg

Продвигаться будем от простого к сложному, от скучного к веселому. Заранее приношу извинения за обильное использование мантр «функция», «параметр» и «Double», но из песни слов не выкинешь.

1.1. Объекты первого класса Для начала укрепимся с мыслью, что в Swift функции являются носителями гордого статуса объектов первого класса. Это значит, что функцию можно хранить в переменной, передавать как параметр, возвращать в качестве результата работы другой функции. Вводится понятие «типа функции». Этот тип описывает не только тип возвращаемого значения, но и типы входных аргументов.Допустим, у нас есть две похожие функции, которые описывают две математические операции сложения и вычитания: func add (op1: Double, op2: Double) → Double { return op1 + op2 }

func subtract (op1: Double, op2: Double) → Double { return op1 — op2 } Их тип будет описываться следующим образом: (Double, Double) → Double Прочесть это можно так: «Перед нами тип функции с двумя входными параметрами типа Double и возвращаемым значением типа Double.«Мы можем создать переменную такого типа: // Описываем переменную var operation: (Double, Double) → Double

// Смело присваиваем этой переменной значение // нужной нам функции, в зависимости от каких-либо условий: for i in 0…<2 { if i == 0 { operation = add } else { operation = subtract } let result = operation(1.0, 2.0) // "Вызываем" переменную println(result) } Код, описанный выше, выведет в консоли:3.0-1.01.2. Замыкания Используем еще одну привилегию объекта первого класса. Возвращаясь к предыдущему примеру, мы могли бы создать такую новую функцию, которая бы принимала одну из наших старых функций типа (Double, Double) -> Double в качестве последнего параметра. Вот так она будет выглядеть: // (1) func performOperation (op1: Double, op2: Double, operation: (Double, Double) → Double) → Double { // (2) return operation (op1, op2) // (3) } Разберем запутанный синтаксис на составляющие. Функция performOperation принимает три параметра: op1 типа Double (op — сокращенное от «операнд») op2 типа Double operation типа (Double, Double) → Double В своем теле performOperation просто возвращает результат выполнения функции, хранимой в параметре operation, передавая в него первых два своих параметра.Пока что выглядит запутанно, и, возможно, даже не понятно. Немного терпения, господа.Давайте теперь передадим в качестве третьего аргумента не переменную, а анонимную функцию, заключив ее в фигурные {} скобки. Переданный таким образом параметр и будет называться замыканием:

let result = performOperation (1.0, 2.0, {(op1: Double, op2: Double) → Double in return op1 + op2 // (5) }) // (4) println (result) // Выводит 3.0 в консоли Отрывок кода (op1: Double, op2: Double) → Double in — это, так сказать, «заголовок» замыкания. Состоит он из: псевдонимов op1, op2 типа Double для использования внутри замыкания возвращаемого значения замыкания → Double ключевого слова in Еще раз о том, что сейчас произошло, по пунктам:(1) Объявлена функция performOperation (2) Эта функция принимает три параметра. Два первых — операнды. Последний — функция, которая будет выполнена над этими операндами.(3) performOperation возвращает результат выполнения операции.(4) В качестве последнего параметра в performOperation была передана функция, описанная замыканием.(5) В теле замыкания указывается, какая операция будет выполняться над операндами. Авторы Swift приложили немало усилий, чтобы пользователи языка могли писать как можно меньше кода и как можно больше тратить свое драгоценное время на чтение Хабра размышления об архитектуре проекта. Взяв за основу наш пример с арифметическими операциями, посмотрим, до какого состояния мы сможем его «раскрутить».2.1. Избавляемся от типов при вызове. Во-первых, можно не указывать типы входных параметров в замыкании явно, так как компилятор уже знает о них. Вызов функции теперь выглядит так: performOperation (1.0, 2.0, {(op1, op2) → Double in return op1 + op2 }) 2.2. Используем синтаксис «хвостового замыкания». Во-вторых, если замыкание передается в качестве последнего параметра в функцию, то синтаксис позволяет сократить запись, и код замыкания просто прикрепляется к хвосту вызова: performOperation (1.0, 2.0) {(op1, op2) → Double in return op1 + op2 } 2.3. Не используем ключевое слово «return». Приятная (в некоторых случаях) особенность языка заключается в том, что если код замыкания умещается в одну строку, то результат выполнения этой строки автоматичеси будет возвращен. Таким образом ключевое слово «return» можно не писать: performOperation (1.0, 2.0) {(op1, op2) → Double in op1 + op2 } 2.4. Используем стенографические имена для параметров. Идем дальше. Интересно, что Swift позволяет использовать так называемые стенографические (англ. shorthand) имена для входных параметров в замыкании. Т.е. каждому параметру по умолчанию присваивается псевдоним в формате $n, где n — порядковый номер параметра, начиная с нуля. Таким образом, нам, оказывается, даже не нужно придумывать имена для аргументов. В таком случае весь «заголовок» замыкания уже не несет в себе никакой смысловой нагрузки, и его можно опустить: performOperation (1.0, 2.0) { $0 + $1 } Согласитесь, эта запись уже совсем не похожа на ту, которая была в самом начале.2.5. Ход конем: операторные функции. Все это были еще цветочки. Сейчас будет ягодка.Давайте посмотрим на предыдущую запись и зададимся вопросом, что уже знает компилятор о замыкании? Он знает количество параметров (2) и их типы (Double и Double). Знает тип возвращаемого значения (Double). Так как в коде замыкания выполняется всего одна строка, он знает, что ему нужно возвращать в качестве результата его выполнения. Можно ли упростить эту запись как-то еще? Оказывается, можно. Если замыкание работает только с двумя входными аргументами, в качестве замыкания разрешается передать операторную функцию, которая будет выполняться над этими аргументами (операндами). Теперь наш вызов будет выглядеть следующим образом: performOperation (1.0, 2.0, +) Красота! Теперь можно производить элементарные операции над нашими операндами в зависимости от некоторых условий, написав при этом минимум кода.Кстати, Swift также позволяет использовать операции сравнения в качестве операторной фуниции. Выглядеть это будет примерно так:

func performComparisonOperation (op1: Double, op2: Double, operation: (Double, Double) → Bool) → Bool { return operation (op1, op2) }

println (performComparisonOperation (1.0, 1.0, >=)) // Выведет «true» println (performComparisonOperation (1.0, 1.0, <)) // Выведет "false" Или битовые операции: func performBitwiseOperation(op1: Bool, op2: Bool, operation: (Bool, Bool) -> Bool) → Bool { return operation (op1, op2) }

println (performBitwiseOperation (true, true, ^)) // Выведет «false» println (performBitwiseOperation (true, false, |)) // Выведет «true» Swift — в некотором роде забавный язык программирования. Надеюсь, статья будет полезной для тех, кто начинает знакомиться с этим языком, а также для тех, кому просто интересно, что там происходит у разработчиков под iOS и Mac OS X.

© Habrahabr.ru