Овладейте одним из самых фундаментальных строительных блоков в программировании

Все языки программирования уникальны. Они хотя бы в чем-то отличаются от всех своих сверстников.

Swift, например, имеет необязательные параметры, псевдонимы типов и непрозрачные возвращаемые типы.

Тем не менее, есть некоторые основные строительные блоки, которые используются почти в каждом языке. Условные выражения позволяют запускать определенную часть кода только в том случае, если условие оценивается как истинное. Большинство языков используют операторы if для реализации этой условной логики.



Но условные операторы — не единственный базовый строительный блок, используемый в большинстве языков программирования. У нас есть классы, объекты, функции, переменные, константы, управление доступом и многое другое.

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

Вот почему сегодня я хочу рассказать об одной из фундаментальных частей языка программирования Swift.

Циклы.

Читая этот пост, вы узнаете следующее:

  1. Что такое петли?
  2. Цикл while.
  3. Цикл «повторяй во время».
  4. Воссоздание классического цикла for в Swift.
  5. Основанный на диапазоне for-in-loop.
  6. Перебор последовательностей с помощью цикла for-in-loop.
  7. Альтернативы использованию циклов.

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

Что такое петли?

Циклы содержат код, предназначенный для выполнения не только один раз, но и несколько раз.

Представьте себе мир без циклов, и вы хотите напечатать числа от 0 до 100.

Вам нужно будет написать 100 операторов печати! Теперь подумайте о более динамичном поведении, когда вы хотите запускать определенные фрагменты кода в зависимости от количества элементов данных, которые есть у пользователя. Это было бы просто невозможно реализовать!

Циклы позволяют вам указать условие и запустить некоторый код за столько итераций, сколько потребуется, чтобы условие оценивалось как ложное.

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

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

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

Цикл while в Swift

Если вам повезет, как мне, и у вас есть степень в области компьютерных наук, вы будете знать этот тип цикла.

Это первый цикл, который вы увидите на многих типичных занятиях по «введению в программирование». Впервые я столкнулся с ним в языке C, а затем в Java.

Давайте сначала рассмотрим пример кода на Swift, прежде чем обсуждать это дальше.

Приведенный выше цикл выводит на консоль числа от 10 до 0 (включая 10 и 0).

Цикл while в основном делает следующее:

Запустите этот фрагмент кода, если условие оценивается как истинное, и столько раз, сколько это условие оценивается как истинное.

Переменная countdown используется для условной части цикла while. В первой итерации значение равно 10, поэтому выполняется код внутри блока while.

В конце блока переменная countdown будет уменьшена на 1. Таким образом, на следующей итерации она будет равна 9.

Примечание. Не забудьте последнюю строку кода! Одна из самых простых ошибок, которую вы можете совершить в отношении циклов, — это забыть изменить переменную, которая используется для оценки. Без последней строки блока while мы бы выполнили код неограниченное количество раз. На каждой итерации переменная countdown будет равна 10. Это приведет к зависанию наших приложений и, в конечном итоге, к сбою.

Цикл будет выполняться до тех пор, пока условие оценивается как истинное, в данном случае до тех пор, пока переменная countdown больше или равна 0.

Как только условие оценивается как ложное (countdown становится меньше 0), блок while больше не будет выполняться, и мы продолжим код, следующий за циклом.

Здесь я хочу отметить еще две вещи:

  1. Неважно, где внутри блока вы изменяете переменную, используемую для условия. Текущая итерация будет выполняться до тех пор, пока не завершится, а затем проверяется условие перед следующей итерацией. Таким образом, изменение переменной не действует как оператор break, continue или return. Однако важно, хотите ли вы использовать его в следующем коде!
  2. Вам не нужно использовать счетчик, как это сделал я. Это наиболее распространенный сценарий, но вы также можете использовать все остальное, что можно использовать для условия. Например, вы также можете использовать переменную, которая вначале имеет значение true и становится ложной, когда что-то происходит внутри цикла.

Актуальность: Лично я не так часто использую цикл while. Когда вы переходите на Swift со старого языка программирования, вы можете использовать его довольно регулярно. Но у цикла while есть существенный недостаток. Вы должны самостоятельно изменить переменную, влияющую на условие! Если вы забудете об этом или сделаете ошибку, цикл будет выполняться бесконечно. Swift изо всех сил старается избегать таких человеческих ошибок, и поэтому я думаю, что другие циклы лучше подходят для Swift и ваших приложений.

Цикл повторения в Swift

Вы, наверное, тоже знаете этот тип петли. Обычно он известен под другим именем. Цикл «делай пока».

Примечание. На заре Swift это также называлось do-while. Они изменили соглашение об именах на Repeat-while в Swift 2.0.

Он очень похож на цикл while, но ведет себя иначе на первой итерации. Давайте посмотрим на другой пример.

Примечание. В обоих примерах мы не использовали операторы увеличения или уменьшения (например, howManyIterationsLeft--, --countdown). Эти операторы больше не существуют в Swift и устарели в языковой версии 2.2.

Можете ли вы угадать, как часто будет выполняться этот цикл? Вы можете предположить, что он вообще не запустится, так как условие говорит, что howManyIterationsLeft должно быть больше 0.

В этом загвоздка цикла «повторяй-пока». Он всегда будет выполняться хотя бы один раз! В приведенном выше фрагменте кода значение переменной howManyIterationsLeft будет выведено на консоль. В таком случае это 0.

Повторяющийся цикл в основном делает следующее:

Запустите этот фрагмент кода и после каждой итерации проверяйте, оценивается ли условие как истинное. Если это так, продолжите с другой итерации.

Итак, давайте вспомним различия между циклами while и repeat-while.

Пока-цикл:

  1. Оценивает условие перед запуском итерации цикла.
  2. Работает от 0 до бесконечности.

Повторять во время цикла:

  1. Оценивает условие после выполнения итерации цикла.
  2. Работает от 1 до бесконечности.

Актуальность: циклы while и repeat-while-loop одинаково часто встречаются в Swift. Повторяющийся цикл, возможно, еще меньше используется из-за того, что он проверяет после первой итерации, а это редко бывает полезно. Оба имеют общий недостаток, заключающийся в необходимости помнить о правильном изменении переменной, используемой для условия. Если вы сделаете здесь ошибку программирования, это произойдет за счет сбоя приложения. Для меня это просто кажется подверженным ошибкам, и это не то, о чем Swift.

Воссоздание классического цикла for в Swift

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

  1. Оператор инициализации.
  2. Состояние.
  3. Инкремент/декремент.

Сначала рассмотрим пример на C.

Оператор инициализации — int counter = 1. Затем, разделенные точкой с запятой, у нас есть условие, а затем приращение переменной, которая используется для условия.

Мы по-прежнему должны сами позаботиться об изменении условной переменной, но синтаксис заставляет нас помнить об этом.

В Swift нет такого цикла for, но мы могли бы воссоздать его с помощью функции шаг.

На самом деле это уже цикл for-in, но я хотел сначала показать вам более классный вариант.

Примечание. Будьте осторожны, функция шага не включает конец последовательности (второй параметр to). Следовательно, мы должны передать 11, чтобы напечатать значения от 1 до 10.

Актуальность: я хочу быть честным с вами, я не видел и не использовал for-in-loop с функцией шага за более чем 6 лет программирования Swift. Если вы хотите забыть какую-то часть информации из этого поста, то, очевидно, это решение. Единственная причина, по которой я вижу это, заключается в том, что вам нужно приращение, отличное от 1. Вы можете передать 2, -5 или 10 параметру by. Тем не менее, Swift имеет гораздо более распространенный тип цикла, который мы хотим рассмотреть далее.

Цикл for-in-loop на основе диапазона в Swift

Мы рассмотрели множество доступных вариантов, и релевантность каждого из них была довольно низкой. Swift предоставляет нам цикл for-in, который может принимать самые разные формы и, безусловно, является самым важным из всех типов циклов.

Давайте начнем с цикла, где нас снова интересует только индекс или счетчик. На этот раз это более быстро.

Его намного легче читать, и он делает именно то, что вы думаете. Он перебирает диапазон, который мы предоставили, включая нижнюю и верхнюю границу. В приведенном выше примере он будет перебирать числа от 1 до 10 и выводить их все на консоль.

Я уже говорил вам, что цикл for в Swift очень универсален. Так можем ли мы также исключить верхнюю границу?

Консоль увидит числа от 0 до 59.

Как видите, оператор полуоткрытого диапазона (..<) работает и в циклах Swift. Вы даже можете использовать константы или переменные для оператора диапазона.

Опять же, нам не нужно заниматься подсчетом самостоятельно.

Как насчет уменьшения в цикле for?

Вы можете просто изменить диапазон следующим образом:

Это по-прежнему будет печатать числа от 0 до 59. Но в обратном порядке, именно то, что мы искали.

Актуальность: я не использую этот метод каждый день, так как есть еще более распространенное решение, которое мы рассмотрим далее. Кроме того, я думаю, вы должны знать, как использовать этот вариант цикла for-in, так как он вам время от времени понадобится. Я обычно использую его 2–3 раза в месяц, и в таких ситуациях полезно знать.

Перебор последовательностей с помощью цикла for-in-loop

Теперь давайте посмотрим на решение, которое я использую чаще всего, когда речь идет о циклах в Swift.

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

Почему мы зацикливаемся в первую очередь? В большинстве случаев это происходит потому, что мы хотим снова и снова выполнять операцию над определенным количеством элементов, которые часто поступают из внешних источников (например, пользовательский ввод или сетевой запрос).

Теперь мы можем получить количество элементов, а затем использовать решение на основе индекса выше. Давайте посмотрим, что в коде:

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

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

1..<animals.count

Это заставит нас не выводить собаку на консоль. Или как насчет этого:

0...animals.count

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

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

Итак, давайте вернемся к приведенному выше примеру с лучшим подходом.

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

Здесь нам не нужно обрабатывать индексы. Мы можем просто использовать элемент.

Как это работает со словарями? Вот код.

Как видите, мы используем тот же цикл, но на этот раз мы можем получить доступ к ключу животного с помощью свойства key и к значению свойства value.

Знаете ли вы, что в Swift можно использовать эмодзи. Вы даже можете зайти так далеко, что будете использовать их как имена переменных, но я бы рекомендовал избегать этого. Это усложняет доступ к ним.

Я опускаю наборы, так как они работают так же, как массивы с точки зрения цикла. Вам просто нужно знать, что наборы не имеют дубликатов. Но будь осторожен! Их тоже не заказывают! Это может иметь некоторые непреднамеренные побочные эффекты.

Но что, если вам все еще нужен индекс элементов? Что, если вы хотите распечатать что-то вроде 0: Собака, 1: Кошка, 2: Рыба? Или по какой-то другой причине вам также нужно знать индекс элемента?

Цикл for-in довольно универсален, и для этого тоже есть решение! Вы можете использовать функцию enumerated для массива и получить доступ как к индексу, так и к элементу.

Перечисленная функция создаст кортеж со структурой (n, x), где n — это индекс элемента, начинающийся с нуля, а x — это сам элемент.

При этом мы можем получить доступ к индексу вместе с фактическим элементом и использовать оба этих значения в одном и том же цикле без необходимости изменять индекс или обращаться к исходному массиву элементов.

Релевантность: я уже упоминал, что предпочитаю этот цикл в этом варианте всем другим типам циклов. Это очень универсально, и вы можете получить доступ к индексу, а также к элементу или к обоим одновременно. Убедитесь, что вы привыкли к синтаксису, потому что вы будете видеть его снова и снова.

Альтернативы использованию циклов в Swift

Есть две альтернативы, о которых вы должны знать, когда дело доходит до циклов.

Рекурсия:

Во-первых, это рекурсия. Рекурсия означает, что у вас есть функция, которая вызывает сама себя, но с другими входными параметрами. Благодаря этому вы можете создать цикл, фактически не имея цикла.

Давайте посмотрим на пример кода.

Мы вызываем функцию printAnimal только один раз (в строке 12). Однако он все равно распечатает всех животных, вызвав себя. Он останавливается, когда мы достигли последнего индекса массива животных, и просто выводит его значение. Вы видите, что петли нет.

Актуальность: мне труднее понять и обдумать рекурсию. Имеет смысл время от времени бросать себе вызов и пробовать разные способы решения общей проблемы. Это расширяет наше мышление и делает нас открытыми для новых решений. Во всяком случае, в производственном коде я склонен использовать циклы вместо рекурсии.

Функции высшего порядка:

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

На самом деле он распечатает всех животных, как мы делали это раньше с циклами.

Функция карты полезна для преобразования элементов коллекции. Функции более высокого порядка можно соединять друг с другом в цепочку, и вы даже можете передавать им функции для сопоставления.

Если у вас осталось больше вопросов, чем ответов, я настоятельно рекомендую это отличное руководство по функциям высшего порядка в Swift:



Актуальность: поскольку в моих последних проектах активно используется функциональное реактивное программирование (FRP), я также все чаще и чаще использую эти функции более высокого порядка. В любом случае, когда я действительно изо всех сил пытаюсь заставить что-то работать, я все равно пытаюсь сначала решить это в цикле, прежде чем переключиться на функции более высокого порядка. Тем не менее, это определенно то, что вам нужно знать, чтобы называть себя экспертом в разработке Swift.

Вывод

На этом мое руководство о циклах в Swift заканчивается. Важно чувствовать себя комфортно с ними, так как вы будете часто их видеть. Независимо от того, являетесь ли вы разработчиком macOS, iOS, tvOS, watchOS или любого другого, вы найдете циклы во многих проектах, в которые вы попадаете.

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

Спасибо за ваше время и получайте удовольствие от петель!