Основы

Swift - новый язык программирования для разработки приложений под iOS, macOS, watchOS и tvOS. Несмотря на это, многие части Swift могут быть вам знакомы из вашего опыта разработки на C и Objective-C.

17 ноября 2022

Swift предоставляет свои собственные версии фундаментальных типов C и Objective-C, включая Int для целых чисел, Double и Float для значений с плавающей точкой, Bool для булевых значений, String для текста. Swift также предоставляет мощные версии трех основных типов коллекций, Array, Set и Dictionary, как описано в разделе Типы коллекций.

Подобно C, Swift использует переменные для хранения и обращения к значениям по уникальному имени. Swift также широко использует переменные, значения которых не могут быть изменены. Они известны как константы, и являются гораздо более мощными, чем константы в C. Константы используются в Swift повсеместно, чтобы сделать код безопаснее и чище в случаях, когда вы работаете со значениями, которые не должны меняться.

В дополнение к знакомым типам, Swift включает расширенные типы, которых нет в Objective-C. К ним относятся кортежи, которые позволяют создавать и передавать группы значений. Кортежи могут возвращать несколько значений из функции как одно целое значение.

Swift также включает опциональные типы, которые позволяют работать с отсутствующими значениями. Опциональные значения говорят либо «здесь есть значение, и оно равно х», либо «здесь нет значения вообще». Опциональные типы подобны использованию nil с указателями в Objective-C, но они работают со всеми типами, не только с классами. Опциональные значения безопаснее и выразительнее чем nil указатели в Objective-C, и находятся в сердце многих наиболее мощных особенностей Swift.

Swift - язык типобезопасный, что означает, что Swift помогает вам понять, с какими типами значений ваш код может работать. Если кусок вашего кода ожидает String, безопасность типов не даст вам передать ему Int по ошибке. Кроме того, безопасность типов не позволит вам случайно передать опциональный String куску кода, который ожидает неопциональный String. Безопасность типов позволяет вам улавливать и исправлять ошибки как можно раньше в процессе разработки.

Константы и переменные

Константы и переменные связывают имя (например, maximumNumberOfLoginAttempts или welcomeMessage) со значением определенного типа (например, число 10 или строка "Hello"). Значение константы не может быть изменено после его установки, тогда как переменной может быть установлено другое значение в будущем.

Объявление констант и переменных

Константы и переменные должны быть объявлены, перед тем как их использовать. Константы объявляются с помощью ключевого слова let, а переменные с помощью var. Вот пример того, как константы и переменные могут быть использованы для отслеживания количества попыток входа, которые совершил пользователь:


let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

Этот код можно прочесть как:

«Объяви новую константу с именем maximumNumberOfLoginAttempts, и задай ей значение 10. Потом, объяви новую переменную с именем currentLoginAttempt, и задай ей начальное значение 0

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

Вы можете объявить несколько констант или несколько переменных на одной строке, разделяя их запятыми:


var x = 0.0, y = 0.0, z = 0.0

Заметка

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

Аннотация типов

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

Этот пример добавляет обозначение типа для переменной с именем welcomeMessage, чтобы обозначить, что переменная может хранить String:


var welcomeMessage: String

Двоеточие в объявлении значит "...типа...", поэтому код выше может быть прочитан как:

«Объяви переменную с именем welcomeMessage, тип которой будет String»

Фраза «тип которой будет String» означает «может хранить любое String значение». Представьте, что словосочетание «тип которой будет такой-то» означает - значение, которое будет храниться.

Теперь переменной welcomeMessage можно присвоить любое текстовое значение, без каких либо ошибок:


welcomeMessage = "Hello"

Вы можете создать несколько переменных одного типа в одной строке, разделенной запятыми, с одной аннотацией типа после последнего имени переменной:


var red, green, blue: Double

Заметка

Редко когда вам понадобится обозначать тип на практике. Когда вы даете начальное значение константе или переменной на момент объявления, Swift всегда может вывести тип, который будет использовать в константе или переменной. Это описано в Строгая типизация и Вывод типов. В примере welcomeMessage выше, не было присвоения начального значения, так что тип переменной welcomeMessage указывается с помощью обозначения типа вместо того, чтобы вывести из начального значения.

Название констант и переменных

Вы можете использовать почти любые символы для названий констант и переменных, включая Unicode-символы:


let π = 3.14159
let 你好 = "你好世界"
let ?? = "dogcow"

Имена констант и переменных не могут содержать пробелы, математические символы, стрелки, приватные (или невалидные) кодовые точки Unicode, а так же символы отрисовки линий или прямоугольников. Так же имена не могут начинаться с цифр, хотя цифры могут быть включены в имя в любом другом месте. Если вы объявили константу или переменную определенного типа, то вы не можете объявить ее заново с тем же именем или заставить хранить внутри себя значение другого типа. Также вы не можете изменить константу на переменную, а переменную на константу.

Заметка

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

Вы можете изменить значение переменной на другое значение совместимого типа. В примере ниже значение friendlyWelcome изменено с “Hello!” на “Bonjour!”:


var friendlyWelcome = “Hello!”
friendlyWelcome = “Bonjour!”
// теперь friendlyWelcome имеет значение “Bonjour!”

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


let languageName = "Swift"
languageName = "Swift++"
// Это ошибка компилляции: languageName cannot be changed (значение languageName не может быть изменено).

Печать констант и переменных

Вы можете напечатать текущее значение константы или переменной при помощи функции print(_:separator:terminator:):


print(friendlyWelcome)
// Выведет "Bonjour!"

Функция print(_:separator:terminator:) является глобальной, которая выводит одно или более значений в подходящем виде. В Xcode, например, функция print(_:separator:terminator:) выводит значения в консоль. Параметры separator и terminator имеют дефолтные значения, так что при использовании функции их можно просто пропустить. По умолчанию функция заканчивает вывод символом переноса строки. Чтобы вывести в консоль значения без переноса на новую строку, вам нужно указать пустую строку в параметре terminator, например, print(someValue, terminator: ""). Для получения дополнительной информации по дефолтным значениям параметров обратитесь в раздел "Значения по умолчанию для параметров".

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


print("Текущее значение friendlyWelcome равно \(friendlyWelcome)")
// Выведет "Текущее значение friendlyWelcome равно Bonjour!"

Заметка

Все опции, которые вы можете использовать в интерполяции строки вы сможете найти в разделе "Интерполяция строк".

Комментарии

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

Комментарии в Swift очень похожи на комментарии в C. Однострочные комментарии начинаются с двух слешей (//):


// это комментарий

Вы также можете написать многострочные комментарии, которые начинаются со слеша и звездочки (/*) и заканчиваются звездочкой, за которой следует слеш (*/):


/* это тоже комментарий,
но написанный на двух строках */

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


/* это начало первого многострочного комментария
/* это второго, вложенного многострочного комментария */
это конец первого многострочного комментария */

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

Точки с запятой

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


let cat = "?"; print(cat)
// Выведет "?"

Целые числа

Integer (целое число) - это число, не содержащее дробной части, например, как 42 и -23. Целые числа могут быть либо знаковыми (положительными, ноль или отрицательными) либо беззнаковыми (положительными или ноль).

Swift предусматривает знаковые и беззнаковые целые числа в 8, 16, 32 и 64 битном форматах. Эти целые числа придерживаются соглашения об именах, аналогичных именам в C, в том, что 8-разрядное беззнаковое целое число имеет тип Uint8, а 32-разрядное целое число имеет тип Int32. Как и все типы в Swift, эти типы целых чисел пишутся с заглавной буквы.

Границы целых чисел

Вы можете получить доступ к минимальному и максимальному значению каждого типа целого числа с помощью его свойств min и max:


let minValue = UInt8.min // minValue равен 0, а его тип UInt8
let maxValue = UInt8.max // maxValue равен 255, а его тип UInt8

Тип значения этих свойств соответствует размеру числа (в примере выше этот тип UInt8) и поэтому может быть использован в выражениях наряду с другими значениями того же типа.

Int

В большинстве случаев вам не нужно будет указывать конкретный размер целого числа для использования в коде. В Swift есть дополнительный тип целого числа - Int, который имеет тот же размер что и разрядность системы:

  • На 32-битной платформе, Int того же размера что и Int32
  • На 64-битной платформе, Int того же размера что и Int64

Если вам не нужно работать с конкретным размером целого числа, всегда используйте в своем коде Int для целых чисел. Это придает коду логичность и совместимость. Даже на 32-битных платформах, Int может хранить любое значение в пределах -2 147 483 648 и 2 147 483 647, а этого достаточно для многих диапазонов целых чисел.

UInt

Swift также предусматривает беззнаковый тип целого числа - UInt, который имеет тот же размер что и разрядность системы:

  • На 32-битной платформе, UInt того же размера что и UInt32
  • На 64-битной платформе, UInt того же размера что и UInt64

Заметка

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

Числа с плавающей точкой

Число с плавающей точкой - это число с дробной частью, например как 3.14159, 0.1, и -273.15.

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

  • Double - представляет собой 64-битное число с плавающей точкой. Используйте его когда число с плавающей точкой должно быть очень большим или чрезвычайно точным
  • Float - представляет собой 32-битное число с плавающей точкой. Используйте его, когда значение не нуждается в 64-битной точности.

Заметка

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

Строгая типизация и Вывод типов

Swift - язык со строгой типизацией. Язык со строгой типизацией призывает вас иметь четкое представление о типах значений, с которыми может работать ваш код. Если часть вашего кода ожидает String, вы не сможете передать ему Int по ошибке.

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

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

Благодаря выводу типов, Swift требует гораздо меньше объявления типов, чем языки, такие как C или Objective-C. Константам и переменным все же нужно присваивать тип, но большая часть работы с указанием типов будет сделана за вас.

Вывод типов особенно полезен, когда вы объявляете константу или переменную с начальным значением. Часто это делается путем присвоения литерального значения (или литерала) к константам или переменным в момент объявления​​. (Литеральное значение - значение, которое появляется непосредственно в исходном коде, например как 42 и 3,14159 в примерах ниже.)

Например, если вы присваиваете литеральное значение 42 к новой константе не сказав какого она типа, Swift делает вывод, что вы хотите чтобы константа была Int, потому что вы присвоили ей значение, которое похоже на целое число:


let meaningOfLife = 42
// meaningOfLife выводится как тип Int

Точно так же, если вы не указали тип для литерала с плавающей точкой, Swift делает вывод, что вы хотите создать Double:


let pi = 3.14159
// pi выводится как тип Double

Swift всегда выбирает Double (вместо Float), когда выводит тип чисел с плавающей точкой.

Если объединить целые литералы и литералы с плавающей точкой в одном выражении, в этом случае тип будет выводиться как Double:


let anotherPi = 3 + 0.14159
// anotherPi тоже выводится как тип Double

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

Числовые литералы

Числовые литералы могут быть написаны как:

  • Десятичное число, без префикса
  • Двоичное число, с префиксом 0b
  • Восьмеричное число, с префиксом 0o
  • Шестнадцатеричное число, с префиксом 0x

Все эти литералы целого числа имеют десятичное значение 17:


let decimalInteger = 17
let binaryInteger = 0b10001 // 17 в двоичной нотации
let octalInteger = 0o21 // 17 в восмеричной нотации
let hexadecimalInteger = 0x11 // 17 в шестнадцатеричной нотации

Литералы с плавающей точкой могут быть десятичными (без префикса) или шестнадцатеричными (с префиксом 0x). Они всегда должны иметь число (десятичное или шестнадцатеричное) по обе стороны от дробной точки. Они также могут иметь экспоненту, с указанием в верхнем или нижнем регистре е для десятичных чисел с плавающей точкой, или в верхнем или нижнем регистре р для шестнадцатеричных чисел с плавающей точкой.

Для десятичных чисел с показателем степени ехр, базовое число умножается на 10exp:

  • 1.25e2 означает 1.25 × 102, или 125.0.
  • 1.25e-2 означает 1.25 × 10-2, или 0.0125.

Для шестнадцатеричных чисел с показателем степени ехр, базовое число умножается на 2exp:

  • 0xFp2 означает 15 × 22, или 60.0.
  • 0xFp-2 означает 15 × 2-2, или 3.75.

Все эти числа с плавающей точкой имеют десятичное значение 12.1875:


let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

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


let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

Преобразования числовых типов

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

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

Преобразования целых чисел

Диапазон значений, который может храниться в целочисленных константах и переменных, различен для каждого числового типа. Int8 константы и переменные могут хранить значения между -128 и 127, тогда как UInt8 константы и переменные могут хранить числа между 0 и 255. Если число не подходит для переменной или константы с определенным размером, выводится ошибка во время компиляции:


let cannotBeNegative: UInt8 = -1
// UInt8 не может хранить отрицательные значения, поэтому эта строка выведет ошибку
let tooBig: Int8 = Int8.max + 1
// Int8 не может хранить число больше своего максимального значения,
// так что это тоже выведет ошибку

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

Чтобы преобразовать один числовой тип в другой, необходимо создать новое число желаемого типа из существующего значения. Ниже, в примере, константа twoThousand имеет тип UInt16, тогда как константа one - UInt8. Сложить их напрямую не получится, потому что они разного типа. Вместо этого, в примере вызывается функция UInt16(one) для создания нового числа UInt16 из значения константы one:


let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

Теперь, из-за того, что обе части сложения имеют тип UInt16 - операция сложения допустима. Для конечной константы (twoThousandAndOne) выведен тип UInt16, потому что это сложение двух UInt16 значений.

НазваниеТипа(начальноеЗначение) - стандартный способ вызвать инициализатор типов Swift и передать начальное значение. Честно говоря, у UInt16 есть инициализатор, который принимает UInt8 значение, и, таким образом, этот инициализатор используется, чтобы создать новый UInt16 из существующего UInt8. Здесь вы не можете передать любой тип, однако это должен быть тип, для которого у UInt16 есть инициализатор. Расширение существующих типов с помощью создания инициализаторов, которые принимают новые типы (включая объявление вашего типа) рассматривается в главе Расширения.

Преобразования целых чисел и чисел с плавающей точкой

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


let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi равно 3.14159, и для него выведен тип Double

Здесь, значение константы three используется для создания нового значения типа Double, так что обе части сложения имеют один тип. Без этого преобразования сложение не будет проходить. Обратное преобразование числа с плавающей точкой в целое число тоже должно происходить явно. Так что тип целого числа может быть инициализирован с помощью Double и Float значений:


let integerPi = Int(pi)
// integerPi равен 3, и для него выведен тип Int

Числа с плавающей точкой всегда урезаются, когда вы используете инициализацию целого числа через этот способ. Это означает, что 4.75 будет 4, а -3.9 будет -3.

Заметка

Правила объединения числовых констант и переменных отличается от правил числовых литералов. Литеральное значение 3 может напрямую сложиться с литеральным значением 0.14159, потому что числовые литералы сами по себе не имеют явного типа. Их тип выводится только в момент оценки значения компилятором.

Псевдонимы типов

Псевдонимы типов задают альтернативное имя для существующего типа. Можно задать псевдоним типа с помощью ключевого слова typealias.

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


typealias AudioSample = UInt16

После того как вы один раз задали псевдоним типа, вы можете использовать псевдоним везде, где вы хотели бы его использовать


var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound теперь 0

Здесь AudioSample определен как псевдоним для UInt16. Поскольку это псевдоним, вызов AudioSample.min фактически вызовет UInt16.min, что показывает начальное значение 0 для переменной maxAmplitudeFound.

Логические типы

В Swift есть простой логический тип Bool. Этот тип называют логическим, потому что он может быть только true или false. Swift предусматривает две логические константы, true и false соответственно:


let orangesAreOrange = true
let turnipsAreDelicious = false

Типы для orangesAreOrange и turnipsAreDelicious были выведены как Bool, исходя из того факта, что мы им присвоили логические литералы. Так же как с Int и Double в предыдущих главах, вам не нужно указывать константы или переменные как Bool, если при создании вы присвоили им значения true или false. Вывод типов помогает сделать код Swift кратким и читабельным тогда, когда вы создаете константы или переменные со значениями которые точно известны.

Логические значения очень полезны когда вы работаете с условными операторами, такими как оператор if:


if turnipsAreDelicious {
 print("Mmm, tasty turnips!")
} else {
 print("Eww, turnips are horrible.")
}
// Выведет "Eww, turnips are horrible."

Условные операторы, такие как оператор if детально рассматриваются в главе Управление потоком.

Строгая типизация Swift препятствует замене значения Bool на не логическое значение. Следующий пример выведет ошибку компиляции:


let i = 1
if i {
 // этот пример не скомпилируется, и выдаст ошибку компиляции
}

Тем не менее, альтернативный пример ниже правильный:


let i = 1
if i == 1 {
 // этот пример выполнится успешно
}

Результат сравнения i == 1 имеет тип Bool, и поэтому этот второй пример совершает проверку типов. Такие сравнения как i == 1 обсуждаются в главе Базовые операторы.

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

Кортежи

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

В данном примере (404, "Not Found") это кортеж, который описывает код HTTP статуса. Код HTTP статуса — особое значение, возвращаемое веб-сервером каждый раз, когда вы запрашиваете веб-страницу. Код статуса 404 Not Found возвращается, когда вы запрашиваете страницу, которая не существует.


let http404Error = (404, "Not Found")
// http404Error имеет тип (Int, String), и равен (404, "Not Found")

Чтобы передать код статуса, кортеж (404, "Not Found") группирует вместе два отдельных значения Int и String: число и понятное человеку описание. Это может быть описано как "кортеж типа (Int, String)".

Вы можете создать кортеж с любой расстановкой типов, и они могут содержать сколько угодно нужных вам типов. Ничто вам не мешает иметь кортеж типа (Int, Int, Int), или типа (String, Bool), или же с любой другой расстановкой типов по вашему желанию.

Вы можете разложить содержимое кортежа на отдельные константы и переменные, к которым можно получить доступ привычным способом:


let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// Выведет "The status code is 404"
print("The status message is \(statusMessage)")
// Выведет "The status message is Not Found"

Если вам нужны только некоторые из значений кортежа, вы можете игнорировать части кортежа во время разложения с помощью символа подчеркивания (_):


let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// Выведет "The status code is 404"

В качестве альтернативы можно получать доступ к отдельным частям кортежа, используя числовые индексы, начинающиеся с нуля:


print("The status code is \(http404Error.0)")
// Выведет "The status code is 404"
print("The status message is \(http404Error.1)")
// Выведет "The status message is Not Found"

Вы можете давать имена отдельным элементам кортежа во время объявления:


let http200Status = (statusCode: 200, description: "OK")

Когда вы присвоили имя элементу кортежа, вы можете обратиться к нему по имени:


print("The status code is \(http200Status.statusCode)")
// Выведет "The status code is 200"
print("The status message is \(http200Status.description)")
// Выведет "The status message is OK"

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

Заметка

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

Опциональные типы (опционалы)

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

Заметка

В C или Objective-C нет понятия опционалов. Ближайшее понятие в Objective-C это возможность вернуть nil из метода, который в противном случае вернул бы объект. В этом случае nil обозначает «отсутствие допустимого объекта». Тем не менее, это работает только для объектов, и не работает для структур, простых типов C, или значений перечисления. Для этих типов, методы Objective-C, как правило, возвращают специальное значение (например, NSNotFound), чтобы указать отсутствие значения. Этот подход предполагает, что разработчик, который вызвал метод, знает, что есть это специальное значение и что его нужно учитывать. Опционалы Swift позволяют указать отсутствие значения для абсолютно любого типа, без необходимости использования специальных констант.

Приведем пример, который покажет, как опционалы могут справиться с отсутствием значения. У типа Int в Swift есть инициализатор, который пытается преобразовать значение String в значение типа Int. Тем не менее, не каждая строка может быть преобразована в целое число. Строка "123" может быть преобразована в числовое значение 123, но строка "hello, world" не имеет очевидного числового значения для преобразования.

В приведенном ниже примере используется метод Int() для попытки преобразовать String в Int:


let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// для convertedNumber выведен тип "Int?", или "опциональный Int"

Поскольку метод Int() может иметь недопустимый аргумент, он возвращает опциональный Int, вместо Int. Опциональный Int записывается как Int?, а не Int. Знак вопроса означает, что содержащееся в ней значение является опциональным, что означает, что он может содержать некое Int значение, или он может вообще не содержать никакого значения. (Он не может содержать ничего другого, например, Bool значение или значение String. Он либо Int, либо вообще ничто)

nil

Мы можем установить опциональную переменную в состояние отсутствия значения, путем присвоения ему специального значения nil


var serverResponseCode: Int? = 404
// serverResponseCode содержит реальное Int значение 404
serverResponseCode = nil
// serverResponseCode теперь не содержит значения

Заметка

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

Если объявить опциональную переменную без присвоения значения по умолчанию, то переменная автоматически установится в nil для вас:


var surveyAnswer: String?
// surveyAnswer автоматически установится в nil

Заметка

nil в Swift не то же самое что nil в Objective-C. В Objective-C nil является указателем на несуществующий объект. В Swift nil не является указателем, а является отсутствием значения определенного типа. Устанавливаться в nil могут опционалы любого типа, а не только типы объектов.

Инструкция If и Принудительное извлечение

Вы можете использовать инструкцию if, сравнивая опционал с nil, чтобы проверить, содержит ли опционал значение. Это сравнение можно сделать с помощью оператора «равенства» (==) или оператора «неравенства» (!=).

Если опционал имеет значение, он будет рассматриваться как «неравным» nil:


if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// Выведет "convertedNumber contains some integer value."

Если вы уверены, что опционал содержит значение, вы можете получить доступ к его значению, добавив восклицательный знак (!) в конце имени опционала. Восклицательный знак фактически говорит: «Я знаю точно, что этот опционал содержит значение, пожалуйста, используй его». Это выражение известно как Принудительное извлечение значения опционала:


if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Выведет "convertedNumber has an integer value of 123."

Более подробную информацию об инструкции if можно получить в главе Управление потоком.

Заметка

Попытка использовать ! к несуществующему опциональному значению вызовет runtime ошибку. Всегда будьте уверены в том, что опционал содержит не-nil значение, перед тем как использовать ! чтобы принудительно извлечь это значение.

Привязка опционалов

Можно использовать Привязку опционалов, чтобы выяснить содержит ли опционал значение, и если да, то сделать это значение доступным в качестве временной константы или переменной. Привязка опционалов может использоваться с инструкциями if и while, для проверки значения внутри опционала, и извлечения этого значения в константу или переменную, в рамках одного действия. Инструкции if и while более подробно представлены в главе Управление потоком.

Привязку опционалов для инструкции if можно писать как показано ниже:

  1. if let constantName = someOptional {
  2.     statements
  3. }

Мы можем переписать пример possibleNumber сверху, используя привязку опционалов, а не принудительное извлечение:


if let actualNumber = Int(possibleNumber) {
    print("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
    print("\(possibleNumber) could not be converted to an integer")
}
// Выведет "123" has an integer value of 123

Это может быть прочитано как:

«Если опциональный Int возвращаемый Int(possibleNumber) содержит значение, установи в новую константу с названием actualNumber значение, содержащееся в опционале.»

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

Вы можете использовать и константы и переменные для привязки опционалов. Если вы хотели использовать значение actualNumber внутри первого ветвления инструкции if, вы могли бы написать if var actualNumber вместо этого, и значение, содержащееся в опционале, будет использоваться как переменная, а не константа.

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


if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// Выведет "4 < 42 < 100"
 
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// Выведет "4 < 42 < 100"

Заметка

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

Неявно извлеченные опционалы

Как описано выше, опционалы показывают, что константам или переменным разрешено не иметь «никакого значения». Опционалы можно проверить с помощью инструкции if, чтобы увидеть существует ли значение, и при условии, если оно существует, можно извлечь его с помощью привязки опционалов для доступа к опциональному значению.

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

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

Вместо того, чтобы ставить восклицательный знак (!) после опционала, когда вы его используете, поместите восклицательный знак (!) после опционала, когда вы его объявляете.

Неявно извлеченные опционалы полезны, когда известно, что значение опционала существует непосредственно после первого объявления опционала, и точно будет существовать после этого. Неявно извлечённые опционалы в основном используются во время инициализации класса, как описано в разделе "Бесхозные ссылки и неявно извлеченные опциональные свойства".

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


let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // необходим восклицательный знак

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // восклицательный знак не нужен

Вы можете считать неявно извлеченные опционалы обычными опционалами дающими разрешение на принудительное извлечение, если это требуется. Когда вы используете неявно извлеченный опционал, то Swift сначала пробует использовать его в качестве обычного опционального значения, если так его использовать не получается, то уже пробует принудительно извлечь значение. В коде выше опциональное значение assumedString является принудительно извлеченным прежде, чем будет записано в implicitString, так как implicitString имеет явный неопциональный тип String. В коде ниже optionalString не имеет явного типа, так что является обычным опционалом.


let optionalString = assumedString
// Тип optionalString является "String?" и assumedString не является принудительно извлеченным значением.

Если вы попытаетесь получить доступ к неявно извлеченному опционалу когда он не содержит значения - вы получите runtime ошибку. Результат будет абсолютно тот же, если бы вы разместили восклицательный знак после нормального опционала, который не содержит значения.

Вы можете проверить не является ли неявно извлеченный опционал nil точно так же как вы проверяете обычный опционал:


if assumedString != nil {
    print(assumedString!)
}
// Выведет "An implicitly unwrapped optional string."

Вы также можете использовать неявно извлеченный опционал с привязкой опционалов, чтобы проверить и извлечь его значение в одном выражении:


if let definiteString = assumedString {
  print(definiteString)
}
// Выведет "An implicitly unwrapped optional string."

Заметка

Не используйте неявно извлечённый опционал, если существует вероятность, что в будущем переменная может стать nil. Всегда используйте нормальный тип опционала, если вам нужно проверять на nil значение в течение срока службы переменной.

Обработка ошибок

Вы используете обработку ошибок в ответ на появление условий возникновения ошибок во время выполнения программы.

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

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


func canThrowAnError() throws {
// эта функция может сгенерировать ошибку
}

Функция сообщает о возможности генерации ошибки, включив ключевое слово throws в объявление. Когда вы вызываете функцию, которая может выбросить ошибку, вы добавляете ключевое слово try в выражение. Swift автоматически передает ошибки из их текущей области, пока они не будут обработаны условием catch.


do {
  try canThrowAnError()
  // ошибка не была сгенерирована
} catch {
  // ошибка сгенерирована
}

Выражение do создает область, содержащую объект, которая позволяет ошибкам быть переданными в одно или несколько условий catch.

Вот пример того, как обработка ошибок может быть использована в ответ на различные условия возникновения ошибок:


func makeASandwich() throws {
    // ...
}
 
do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

В этом примере, функция makeASandwich() генерирует ошибку, если нет чистых тарелок или если отсутствуют какие-либо ингредиенты. Так как makeASandwich() может выдавать ошибку, то вызов функции заворачивают в выражении try. При заворачивании вызова функции в выражение do, генерация каких-либо ошибок, будет передаваться на предусмотренные условия catch.

Если ошибка не генерируется, то вызывается функция eatASandwich(). Если ошибка все таки генерируется, и она соответствует SandwichError.outOfCleanDishes, то вызывается функция washDishes(). Если генерируется ошибка, и она соответствует SandwichError.missingIngredients , то функция buyGroceries(_:) вызывается с соответствующим значением [String], захваченным шаблоном catch.

Генерация, вылавливание и передача ошибок рассмотрены более подробно в главе Обработка ошибок.

Утверждения и предусловия

Утверждения и предусловия являются проверками во время исполнения. Вы используете их для того, чтобы убедиться, что какое-либо условие уже выполнено, прежде чем начнется исполнение последующего кода. Если булево значение в утверждении или в предусловии равно true, то выполнение кода просто продолжается далее, но если значение равно false, то текущее состояние исполнения программы некорректно и выполнение кода останавливается и ваше приложение завершает работу.

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

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

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

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

Отладка с помощью утверждений

Утверждения записываются как функция стандартной библиотеки Swift assert(_:_:file:line:). Вы передаете в эту функцию выражение, которые оценивается как true или false и сообщение, которое должно отображаться, если результат условия будет false. Например:


let age = -3
assert(age >= 0, "Возраст человека не может быть меньше нуля")
// это приведет к вызову утверждения, потому что age >= 0, а указанное значение < 0.

В этом примере, выполнение кода продолжится, только если age >= 0 вычислится в true, что может случиться, если значение age не отрицательное. Если значение age отрицательное, как в коде выше, тогда age >= 0 вычислится как false, и запустится утверждение, завершив за собой приложение.

Сообщение утверждения можно пропускать по желанию, как в следующем примере:


assert(age >= 0)

Если код уже проверяет условие, то вы используете функцию assertionFailure(_:file:line:) для индикации того, что утверждение не выполнилось. Например:


if age > 10 {
    print("Ты можешь покататься на американских горках и чертовом колесе.")
} else if age > 0 {
    print("Ты можешь покататься на чертовом колесе.")
} else {
    assertionFailure("Возраст человека не может быть отрицательным.")
}

Обеспечение предусловиями

Используйте предусловие везде, где условие потенциально может получить значение false, но для дальнейшего исполнения кода оно определенно должно равняться true. Например, используйте предусловие для проверки того, что значение сабскрипта не вышло за границы диапазона или для проверки того, что в функцию было передано корректное значение.

Для использования предусловий вызовите функцию precondition(_:_:file:line:). Вы передаете этой функции выражение, которое вычисляется как true или false и сообщение, которое должно отобразиться, если условие будет иметь значение как false. Например:


 // В реализации сабскрипта...
precondition(index > 0, "Индекс должен быть больше нуля.")

Вы так же можете вызвать функцию preconditionFailure(_:_:file:line:) для индикации, что отказ работы уже произошел, например, если сработал дефолтный кейс инструкции switch, когда известно, что все валидные значения должны быть обработаны любым кейсом, кроме дефолтного.

Заметка

Если вы компилируете в режиме -0unchecked, то предусловия не проверяются. Компилятор предполагает, что предусловия всегда получают значения true, и он оптимизирует ваш код соответствующим образом. Однако, функция fatalError(_:file:line:) всегда останавливает исполнение, несмотря на настройки оптимизации.

Вы можете использовать функцию fatalError (_:file:line:) во время прототипирования или ранней разработки для создания заглушек для функциональности, которая еще не реализована, написав fatalError ("Unimplemented") в качестве реализации заглушки. Поскольку фатальные ошибки никогда не оптимизируются, в отличие от утверждений или предусловий, вы можете быть уверены, что выполнение кода всегда прекратится, если оно встречает реализацию заглушки.

Содержание