«Что нового в Swift 2?» на примерах. Часть 1.

Screen Shot 2015-10-28 at 3.18.06 PM
Swift 2 сфокусировался на улучшении самого языка, улучшении взаимодействия с  Objective-C и повышении производительности компилируемых приложений. Новые возможности Swift 2 представлены в 6 различных областях:

  • фундаментальные конструкции языка, такие, как enums, scoping (область действия), синтаксис аргументов и т.д.
  •  сопоставление с образцом (pattern matching)
  • проверка доступности (availability checking)
  • расширения (extensions) протокола
  •  управление ошибками (error handling)
  • взаимодействие с Objective-C

Я буду рассматривать новые возможности Swift 2, сопровождая их примерами, код которых находится на Github.

1. Фундаментальные конструкции языка

Больше нет println ( )

Обычно мы использовали функцию println( ) для печати сообщения на консоли. В версии Swift 2.0 мы будем использовать только print( ). Apple скомбинировала обе функции println( ) и print( ) в одну. Функция print( ), по умолчанию, печатает ваше сообщение с символом «\n» новой строки, если вы хотите, можно вывести строку без символа новой строки:

Screen Shot 2015-10-20 at 12.42.32 PM

 

map, filter & co

Определение этих удобных функций и методов через коллекции в Swift 1.2 было не вполне согласованным.

В Swift 1.2, не было реализации по умолчанию map для протокола CollectionType, так как умолчательная реализация в протокола была невозможна  и расширения (extensions) были сделаны только для классов. Частично поэтому, map была определена как метод в классе Array (который реализует протокол CollectionType), и не определена в классе Set. В дополнение к этому, глобальная функция map была задекларирована таким образом, что она принимала экземпляр  CollectionType в качестве первого параметра. Это создавало полную путаницу.

[js]
// Swift 1.2
let a: [Int] = [1,2,3]

// здесь мы используем map как метод Array
let b = a.map{ $0 + 1 }

// здесь мы вызываем глобально определенную map функцию
map([1,2,3]) { $0 + 1 }

let set = Set([1,2,3])

// не работает, так как нет map метода для Set
set.map{ $0 + 1 }
// глобальная функция map работает на Set
map(set) { $0 + 1 }
[/js]

Получалось, что в зависимости от типа коллекции, используется либо глобальная функция map, либо метод map этой коллекции. Выглядит несогласованным и плохо читаемым, если используется цепочка преобразований с использованием методов и функций map, filter и reduce.

Теперь в Swift 2 разрешены расширения (extensions) протокола (protocol), поэтому map, filter & co реализуются на уровне протокола для CollectionType, как расширения ( extension) протокола. Следовательно, одни и те же методы будут оперировать над Array, Set или любой другой коллекцией, реализующей протокол CollectionType.

[js]
// Swift 2

let a: [Int] = [1,2,3]
let b = a.map{ $0 + 1 }

let set = Set([1,2,3])
let anotherSet = set.map{ $0 + 1 }

let sum = (1…100)
.filter { $0 % 2 != 0 }
.map { $0 * 2 }
.reduce(0) { $0 + $1 }

print(sum)
// prints out 5000
[/js]

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

Перечисления enum

В Swift 2, enums обладают достаточной reflection информацией, чтобы сделать возможной их печать.
Screen Shot 2015-10-16 at 9.11.27 PM
Предложение print (an)  теперь будет корректно печатать Dragon, хотя в предыдущей версии Swift вывод был совершенно не информативным
(Enum Value).
Другое улучшение, касающееся enums состоит в том, что теперь Swift позволяет представлять ассоциированные значения различных типов в  enum. В качестве примера, теперь можно законным образом представить тип Either:

Screen Shot 2015-10-17 at 9.57.32 PM
Теперь enums могут быть рекурсивными, то есть мы можем построить дерево с помощью  enums. Давайте посмотрим на этот пример:

Screen Shot 2015-10-16 at 9.57.09 PM
Мы должны использовать ключевое слово  indirect впереди case Node. И это позволило нам создать дерево:

Screen Shot 2015-10-16 at 9.59.26 PM

Вот как оно выглядит:

Screen Shot 2015-10-16 at 9.53.18 PM
Теперь мы можем создать функцию, которая рекурсивно обходит все дерево и складывает числа:

Screen Shot 2015-10-16 at 10.02.16 PM
Должен быть напечатан результат, равный 21.

Размер строки String изменился. Опять

Apple опять изменила способ расчета длины строки. То, что было в Swift 1.1 countElemets() прератилось в Swift 1.2 в count(), и теперь это все полностью исчезло – и если вы попробуете использовать count () для String, то получите ошибку.

Вместо этого, вам нужно получить свойство characters вашей строки  String и вызвать count(). Вы должны делать так, пока Apple опять не передумала…

Автоматический синтез заголовков.

Это очень незначительное, но очень ожидаемо изменение. Для того, чтобы попробовать его в Xcode 7 нужно пойти в меню Navigate -> Jump to Generated Interface.

Screen Shot 2015-10-22 at 10.12.45 AM

В Objective C файлы заголовка (.h файлы) содержали список функциональных возможностей классов – какие методы и свойства доступны, какие параметры у методов. но без кода.

У Swift нет файлов заголовка (.h файлов), и вы пишите свой код в единственном  .swift файле и не беспокотесь о том, что вам нужно модифицировать файлы заголовка. Вместо этого вы используете ключевое слово private, чтобы пометить, что не следует внешнему миру это показывать..

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

Решение Apple — простое и эффективное: Xcode теперь может показать синтезированные файлы заголовка: он сканирует ваш код и создает вертуальные файлы заголовка, в которых представлены экспонируемые во внешний мир методы без кода.

Screen Shot 2015-10-22 at 10.16.56 AM

Диагностика.

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

Одно из простейших изменений делает код более читабельным. Как вы знаете, Swift разработчики предпочитают декларировать многие вещи как константы, используя let, а не как переменные, используя var. Но вдруг вы случайно использовали ключевое слово var? Или вы подумали, что вам ее нужно изменить, но не сделали этого? Как Xcode 7, так и Swift 2, дадут вам предупреждение, что в своем коде вы нигде эту переменную не изменяете – Xcode буквально исследует все способы использования переменной и точно знает, изменяли вы ее или нет.

Множества опций (Option Sets)

Множества опций — это способ представления множества булевских значений, и в  Swift 1.x это выглядело так:

[js]
viewAnimationOptions = nil
viewAnimationOptions = .Repeat | .CurveEaseIn | .TransitionCurlUp
if viewAnimationOptions & .TransitionCurlUp != nil { …
[/js]

Этот тип синтаксиса широко использовался в Cocoa, но в действительности, это лишь «пережиток» языка C. Так что в Swift 2 он удален и представлен собственный тип для множества опций, это протокол OptionSetType :

Screen Shot 2015-10-17 at 8.23.40 PM

Так что теперь множество опций может быть любым типом Set или struct, подтверждающим  OptionSetType протокол. Это приводит к более понятному синтаксису при использовании множества опций:

Screen Shot 2015-10-17 at 8.39.23 PM
Синтаксис не полагается на «битовые» операции, как в предыдущих версиях, и не использует nil для представления пустого множества опций.
Следует заметить, что множество опций OptionSetType теперь полагается на другую возможность в Swift 2, называемую «умолчательной» реализацией расширений протокола (default implementations for protocol extensions), так что просто подтверждая протокол OptionSetType, вы получаете реализацию по умолчанию, например, для метода contains, subtractInPlace, unionInPlace и других операций над множествами. Мы рассмотрим расширения протокола (protocol extensions) позже.

Функции и методы

Синтаксис Swift 1.x для декларирования функций и методов был унаследован от двух различных соглашений, происходяших соотетственно от C, где аргументы функции не имеют меток, и Objective-C, который снабжает аргументы методов метками. Так что у вас были такие декларации:

func save(name: String, encrypt: Bool) { ... }
class Widget {
  func save(name: String, encrypt: Bool) { ... }
  
save("thing", false)
widget.save("thing", encrypt: false)

В Swift 2, вышеприведенный код будет выглядеть так:

Screen Shot 2015-10-17 at 9.42.45 PM
Так что функции получили то же самое соглашение, что и методы:

  • подразумевается, что имя первого аргумента содержится в имени функции;
  • последующие аргументы имеют метки.

Однако эти изменения не относятся к функциям, импортированным из C и Objective-C APIs.

Дополнительно, модель декларирования меток параметров стала более удобной, так как удалена опция #option, которая использовалась в Swift 1.x для обозначения параметра с одинаковым внутренним (internal) и внешним (external) именем.

Операторы области видимости ( Scoping Operators)

Новое предложение do позволяет разработчикам явно определять область видимости (explicit scope) переменных и констант. Это может быть полезно для повторного использования уже задекларированных имен или для раннего освобождения некоторых ресурсов. Предложение do выглядит так:

Screen Shot 2015-10-17 at 10.27.02 PM

Для того, чтобы избежать неоднозначности с предложением  do … while, которое представлено в ранних версиях Swift 1.х, в Swift 2 последний был переименован в repeat … while.

UNIT- тестирование

Проблема с unit-тестирование кода на Swift 1.x заключается в том, что Swift 1.x заставлял вас помечать словом public все то, что вы хотите, чтобы unit-тестирование видело. В результате остаются пометки  public там, где они не должны быть. Все это связано с тем, что Test Target отличается от Application Target, и файлы из вашего приложения, которые являются internal, не доступны для Test Target.

В Swift 2 достигнуто существенное облегчение в unit-тестирования. Xcode 7 автоматически компилирует Swift 2 код в специальном «тестируемом» режиме

Screen Shot 2015-10-18 at 9.41.14 AM

чтобы получить доступ ко всем internal определениям словно они определены как public. Это делается с помощью @testable атрибута при импорте нашего модуля.

Screen Shot 2015-10-18 at 9.49.39 AM

Это все, что требуется, и вам ничего не нужно метить словом  public.

Причем эти изменения не влияют на основной релиз вашего приложения, сохраняя корректное поведение как с точки зрения производительности, так и с точки зрения управления доступом (access control).

2. Управление порядком вычислений

В Swift 2.х введены новые концепции управления порядком вычислений (control flow), а также усовершенствованы уже существующие конструкции.

Предложение guard

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

Предложение guard, по существу, является инверсией предложения if. Для if мы пишем:

[js]
if condition {
// true ветка
} else {
// false ветка
}
[/js]

Для guard, true ветка поднимается на более высокий уровень по сравнению с false веткой:

[js]
guard condition else {
// false ветка
}
// true ветка
[/js]

Заметьте, что false ветка должна закончить выполнение в закрытом контексте (scope), возвращая значение или «выбрасывая» (throw) ошибку. Вы гарантируете, что код в true ветке будет выполняться только, если условие выполняется.

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

Screen Shot 2015-10-18 at 3.05.21 PM

На вход функции createPersonFromJSON подается словарь jsonDict, а на выходе создается правильный экземпляр структуры Person, если в словаре представлена соответствующая информация, в противном случае возвращается nil. Функция написана так, как она бы выглядела в Swift 1.2 — с использованием конструкции if let. Есть пара «болевых точек» в этом коде. Во-первых, «выключение» направления правильных вычислений  из основного кода, то есть, «удачное» (с точки зрения условия) направление вычислений оказалось «вложенным» в предложение if let. Во-вторых, функция createPersonFromJSON не всегда возвращает экземпляр Person, когда он нам нужен. Структура Person содержит 3 свойства, одно из которых Optional, но функция возвращает правильный экземпляр Person только, если мы получаем отличные от nil значения из словаря для всех 3-х ключей. Давайте перепишем эту функцию так: чтобы мы могли вернуть экземпляр Person, если адрес  отсутствует, то есть если ключ «address» возвращает nil.

Screen Shot 2015-10-18 at 4.23.36 PM
Мы сделали небольшие усовершенствования функциональности. Эта версия createPersonFromJSON2 функции теперь может создавать экземпляр Person даже если у адреса значение nil. Это лучше отражает структуру Person, но теперь у нас множество предложений if, а также необходимость разворачивать финальные значения, присвоенные  name и age. Давайте посмотрим, как это можно усовершенствовать с новым предложением guard.

Screen Shot 2015-10-18 at 5.01.13 PM

В случае guard предложения, также, как и с if let предложением, мы можем проверять присутствие значений, «разворачивать» их и присваивать константам. Однако с  конструкцией guard let выполнение кода продолжается после фигурных скобок { }, если условное выражение оценивается как true. Это означает, что мы можем создать  экземпляр Person внутри нормальной области действия функции без использования дополнительного ветвление кода для разворачивания значений. Если любое из значений name или age равно nil, то выполнение кода «перепрыгивает» на предложение else и осуществляется ранний возврат nil.

Давайте подведем краткие итоги в отношении guard.

  • Если условие guard предложения выполняется, выполнение кода продолжается после закрытия фигурных скобок предложения guard ;
  • Если это условие не выполняется, то выполняется код в else «ветке»; в отличие от if, guard всегда имеет else блок;
  • Предложение else должно передавать управление за пределы нормальной области видимости функции с использованием returnbreakcontinue или путем вызова другой функции или метода.

Предложение defer

Предложение defer напоминает finally в других языках программирования, за исключением того, что оно не привязано к предложению try, и вы можете его использовать где угодно. Вы пишите defer {…} и где-нибудь в коде и этот блок будет выполнен, когда управление вычислением покинет эту область видимости кода (enclosing scope), причем не имеет значения, добирается ли код до конца или получает предложение return или «выбрасывает» ошибку. Оператор defer прекрасно сочетается с  guard и  с обработкой ошибок (рассматривается позже).

[js]
guard let file1 = Open(…) else {
// обрабатываем ошибки file1
return
}
defer { file1.close() }

guard let file2 = Open(…) else {
// обрабатываем ошибки file2
return
}
defer { file2.close() }

// используем file1 и file2
. . . . . . .
// нет необходимости закрывать файлы в конце, все уже сделано
[/js]

Заметим, что defer работает для file1 как при нормальном течении вычислительного процесса, так и в случае ошибки с файлом file2. Это убирает из кода многочисленные повторы и помогает вам не забыть что-то «очистить» в какой-нибудь ветке вычислений. При обработке ошибок стоит та же проблема и предложение defer подходит для этих целей наилучшим образом.

Repeat — while

Swift 2.0 внес синтаксические изменения в предложение do-while, которое использовалось прежде. Вместо do-while, теперь мы получим repeat- while.

Screen Shot 2015-10-18 at 5.54.00 PM
Есть две причины для таких изменений:

Когда вы используете do- while цикл,  сразу же неясно, что это конструкция для повторения. Это особенно справедливо, если блок кода внутри do предложения большой, и условие while находится за пределами экрана. Для смягчения этого обстоятельства, ключевое слово do заменено на repeat, которое проясняет для пользователя, что это повторяющийся блок кода.
Ключевое слово do имеет новое назначение в Swift 2 в новой модели обработки ошибок, исследованием которой мы займемся позже.

Pattern matching

Swift всегда имел мощные возможности pattern matching (соответствие по образцу), но только в конструкции switch. Конструкция switch рассматривала значение value и сравнивала его с несколькими возможными образцами. Одним из недостатков предложения switch является то, что мы должны представить все возможные варианты значения value, то есть оператор switch должен быть исчерпывающим (exhaustive) и это вызывает неудобство использования. Поэтому Swift 2 портировал возможности pattern matching, которые прежде были только у switch / case, другим предложениям, управляющим потоком вычислений. if case — это один из них, и он позволяет переписать код с switch более кратко. Другими предложениями являются for case и while case.

Pattern matching if case

Новым в Swift 2 является поддержка pattern matching внутри предложений if  (и guard). Давайте сначала определим простейшее перечисление Number, а затем покажем способы его применения

Screen Shot 2015-10-18 at 8.53.23 PM

1. Проверка определенного варианта (case)

Используем case: мы хотим проверить, соответствует ли значение определенному case. Это работает несмотря на то, имеет ли этот case ассоциированное значение или нет, но значение не восстанавливается (если оно существует).

Screen Shot 2015-10-18 at 9.03.32 PM
Образец начинается с case .IntegerValue, а значение, которое должно соответствовать этому образцу, переменная myNumber, идет после знака = равенства. Это может показаться нелогичным, но то же самое мы видим при «развертывании» Optional значения a1 в конструкции if let a = a1 : значение a1, которое проверяется, идет после знака равенства.

Вот эквивалентная Swift 1.2 версия, использующая switch:
Screen Shot 2015-10-18 at 10.39.45 PM

2. Получение ассоциированного значения

Используем case: мы хотим проверить, соответствует ли значение определенному case, а также извлечь ассоциированное значение (или значения).

Screen Shot 2015-10-19 at 9.45.58 AM

«Образец» теперь превратился в case let .IntegerValue(theInt). Значение, которое должно соответствовать «образцу» то же, что и в предыдущем примере.

Ниже приведен пример, отражающий ту же самую концепцию, но применительно к guard. Семантика предикатов для guard и if идентичная, так что pattern matching работает точно также.

Screen Shot 2015-10-19 at 10.19.39 AM

 

3. Отбор с помощью предложения where

К любому case в предложении guard может быть добавлено (необязательно) предложение where для обеспечения дополнительных ограничений. Давайте модифицируем функцию getObjectInArray:atIndex: из предыдущего примера:

Screen Shot 2015-10-19 at 10.32.26 AM

4. Соответствие диапазону range

Screen Shot 2015-10-19 at 10.53.27 AM

5. Используем кортеж tuple

Screen Shot 2015-10-19 at 11.00.46 AM

6. Сложные if предикаты

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

  • Простейшие логические тесты (например, foo == 10 || bar > baz). Может быть только один такой предикат и он должен помещаться на первом месте.
  • Разворачивание Optional (например, let foo = maybeFoo where foo > 10). Если за предикатом разворачивания Optional сразу же следует другой предикат разворачивания Optional, то let можно пропустить. Можно дополнить квалификатором where.
  • Pattern matching (например, case let .Bar(something) = theValue), — это то, что мы рассматривали выше. Можно дополнить квалификатором where.

Предикаты оцениваются в порядке их определения и после не выполнения какого-то предиката, остальные не оцениваются.

Pattern matching for case

Pattern matching может использоваться в содружестве с циклом for -in. В этом случае наши намерения состоят в том, чтобы пройтись по элементам последовательности, но только по тем, которые соответствуют заданному «образцу». Вот пример:

Screen Shot 2015-10-19 at 12.42.42 PM

Заметим, что также, как «образцы» в предложении switch, вы можете извлекать множество ассоциированных значений и использовать  _, если вы этим ассоциированным значением не интересуетесь. Если необходимо, вы также можете добавить дополнительные ограничения с помощью предложения where.

Pattern matching while

Pattern matching можно также использовать с while циклом. В этом случае мы будем повторять тело цикла до тех пор, пока некоторое значение в предикате не будет соответствовать «образцу». Вот пример:

Screen Shot 2015-10-19 at 1.04.31 PM

Заметим, что сложные предикаты, описанные в разделе «6. Сложные предикаты if»  также поддерживаются циклом while, включая использование where.

Pattern для «развертывания» (unwrapping) многочисленных Optional

В Swift 1.2, у нас был прекрасный компактный синтаксис для «развертывания» множества  Optionals в одном простом предложении if let:

[js]
var optional1: String?
var optional2: String?

if let optional1 = optional1, let optional2 = optional2 {
print("Success")
} else {
print("Failure")
}
[/js]

Здорово!

Однако, вы все же встречаетесь с ситуацией, когда вам действительно нужно управлять различными комбинациями существующих / пропущенных Optional зачений. Одним из таких примеров является форма для заполнения полей username и password, причем пользователь не заполнил одно из них, и нажал кнопку «Submit«. В этом случае вам захочется показать специальную ошибку, чтобы уведомить пользователя, что конкретно пропущено. Для этого мы можем использовать в Swift 1.x pattern matching!

[js]
var username: String?
var password: String?

switch (username, password) {
case let (.Some(username), .Some(password)):
print("Success!")
case let (.Some(username), .None):
print("Password is missing")
case let (.None, .Some(password)):
print("Username is missing")
case (.None, .None):
print("Both username and password are missing")
}
[/js]

Это немного неуклюже, но мы пользовались этим с самого начала.

В Swift 2 синтаксис выглядит более понятным:

Screen Shot 2015-10-19 at 1.50.32 PM

При первом взгляде смущает использование вопросительного знака ? для того, чтобы показать, что значение присутствует (особенно если это ассоциировать с идеей Optionals, когда значение может существовать, а может и не существовать),  но нужно признать, что этот пример становится очень понятным в отличие от неуклюжего синтаксиса .Some(username).

Обработка ошибок (Error handling)

Чтобы понять новые возможности Swift, относящиеся к обработке ошибок, будет полезно вспомнить, что существует 3 способа, когда функция может заканчиваться аварийно (далее для краткости перейдем на жаргон и будем говорить «падать»):

  • многие функции могут «падать» по одной достаточно простой «врожденной» причине, например, когда вы пытаетесь преобразовать String в Int; такие случаи достаточно хорошо обрабатываются с помощью возвращения Optional значения;
  • на другом конце спектра находятся логические ошибки программирования, которые вызывают выход индекса массива за границы, непреемлемые условия и т.д., с ними очень тяжело иметь дело и мы не знаем, как можно ими управлять.
  • третий случай — это ошибки детализации, поправимые ошибки, например, такие, как не найден файл, или ошибка сети или пользователь уничтожил операцию (ситуационные ошибки).

Обработка ошибок третьего типа, связанных с ситуацией, — вот что пытается улучшить Swift 2.

Если мы рассмотрим типичную схему управления такими ошибками в Swift 1.х и Objective-C, то мы обнаруживаем схему, когда функция получает аргумент inout NSError? и возвращает Bool  для представления успешного или ошибочного завершения операции:

[js]
func preflight(inout error: NSError?) -> Bool {
if (!url.checkResourceIsReachableAndReturnError(&&error)) {
return false
}
resetState()
return true
}
[/js]

У этого подхода есть множество «темных» сторон, которые делают менее понятным, а что собственно метод делает, но более важно то, что требуется ручная реализация соглашений относительно того, что стоит за возвращаемым Bool. Если метод возвращает объект и получил ошибку, то он возвращает nil; если это булевское значение, то возвращается false  и так далее. Вам нужно знать, с каким методом вы имеете дело, что проверять, является ли результат nil или  false или что-то еще, когда метод содержит объект ошибки NSError?. Очень запутанный синтаксис. Все эти трудности связаны с тем, что  Objective-C не мог возвращать множество значений из функции или метода и в случае, если нам нужно уведомить пользователя об ошибке, то предлагался такой укоренившийся способ ее обработки.

Swift 2 получил новое управление ошибками. Он использует синтаксис dotrycatch,  который заменяет NSError.  Давайте посмотрим как можно использовать этот новый синтаксис. Я буду рассматривать очень простой пример обработки таких ошибок, с  которыми вполне справляются возвращаемые Optional значения, и для которых новый синтаксис вообщем-то не предназначен. Но простота этого примера позволит мне акцентировать ваше внимание именно на механизме «выбрасывания» и «ловле» ошибок, а не на сложности их семантического содержания. В конце я приведу реальный пример обработки данных, пришедших из сети.

Прежде чем ошибка может быть выброшена (throw) или поймана (catch), она должна быть определена. Вы можете определить ее в Swift 2 с помощью enum, который реализует новый протокол ErrorType:

Screen Shot 2015-10-19 at 8.07.43 PM

Для того, чтобы функция

Screen Shot 2015-10-19 at 8.59.49 PM

могла выбрасывать (throw) ошибку, нужно анонсировать в заголовке функции ключевое слово throws :

Screen Shot 2015-10-19 at 9.01.51 PM
Теперь эта функция может выбрасывать ошибку, используя ключевое слово throw  и ссылку на конкретный тип ошибки:
Screen Shot 2015-10-19 at 9.25.02 PM
Если вы попытаетесь вызвать эту функцию, то компилятор выдаст ошибку:»Вызываемая функция выбрасывает ошибки, а обращение к ней не помечено ключевым словом try и нет обработки ошибок.»
Screen Shot 2015-10-19 at 9.38.05 PM
Потому что функция объявила, что она способна выбрасывать ошибки, и вы должны «ловить» потенциальные ошибки. Давайте попробуем использовать ключевое слово try:
Screen Shot 2015-10-19 at 9.29.41 PM
Этого оказалось недостаточно, компилятор сообщает нам, что требуется обработка ошибок, которая производится с помощью синтаксической конструкции do-try-catch:
Screen Shot 2015-10-20 at 9.11.16 AM
Более того, в блоке do-try-catch у вас есть возможность «ловить» несколько ошибок:
Screen Shot 2015-10-20 at 9.15.18 AM
Если смысловая часть ошибок вас не интересует, то вместо того, чтобы использовать конструкции do-try-catch, можно обращаться с интересующими нас значениями как с Optional:

Screen Shot 2015-10-20 at 9.27.46 AM

aTry и aTrySuccess являются Optional, так что не забывайте их «разворачивать» перед использованием!

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

Если функция «выбрасывает» ошибку, то она возвращается незамедлительно. Но иногда нужно сделать некоторые действия, например, по освобождению ресурсов или закрытию файлов, прежде, чем функция вернется. В этой ситуации прекрасно работает уже знакомое нам ключевое слово defer. С ключевым словом defer можно определить блок кода, который всегда исполняется, если функция возвращается, и не имеет значения возвращается ли она нормально или из-за ошибок.

Мы можем определить defer блок в любом месте в нашей функции. Более того, возможно определение более одного defer блока. В этом случае они будут выполняться в обратном порядке. Давайте рассмотрим пример:

Screen Shot 2015-10-20 at 10.37.59 AM

Перейдем к рассмотрению реального примера, представленного в статье Natasha Murashev. Swift 2.0: Let’s try? 

Рассмотрим данные, которые приходят с некоторого API (после десериализации).

Screen Shot 2015-10-20 at 11.06.16 AM
Эти данные нужно преобразовать в Модель для последующего использования в приложении

Screen Shot 2015-10-20 at 11.30.51 AM
Парсер TodoItemParser имеет дело со смешанными данными, пришедшими из некоторого API, и преобразует их в понятную Модель для последующего безопасного использования в приложении

Screen Shot 2015-10-20 at 11.37.09 AM

Теперь выполним парсинг «хороших» данных в Модель с использованием конструкции do-try-catch для обработки ошибок в Swift!

Screen Shot 2015-10-20 at 11.49.52 AM
Выполним парсинг «плохих» данных.

Screen Shot 2015-10-20 at 11.53.41 AM

Вместо использования конструкции do-try-catch, можно обращаться с интересующими нас значениями как  с Optional:

Screen Shot 2015-10-20 at 12.05.25 PM

 

В первой части мы рассмотрели лишь часть новых возможностей Swift 2:

  • фундаментальные конструкции языка, такие, как enumsscoping (область действия), синтаксис аргументов и т.д.
  •  сопоставление с образцом (pattern matching)
  •  управление ошибками (error handling)

Во второй части мы рассмотрим оставшиеся:

  • проверка доступности (availability checking)
  • расширения (extensions) протокола
  • взаимодействие с Objective-C

Ссылки на используемые статьи
New features in Swift 2
What I Like in Swift 2
A Beginner’s guide to Swift 2
Error Handling in Swift 2.0
Swift 2.0: Let’s try?
Video Tutorial: What’s New in Swift 2 Part 4: Pattern Matching
Throw What Don’t Throw
The Best of What’s New in Swift

«Что нового в Swift 2?» на примерах. Часть 1.: 2 комментария

    • Пора писать новую статью «Что нового в Swift 3.»

Обсуждение закрыто.