Что нового в Swift 2

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

Имена параметров функций и методов

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

func someFunction(param1: Int, param2: Int, param3: Int) -> Int {
    return param1 + param2 + param3
}

someFunction(5, param2: 6, param3: 7)

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

func someFunction( _ param1: Int, param2 param2: Int, param3 param3: Int) -> Int {
    return param1 + param2 + param3
}

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

try/catch

Апгрейд - это неотъемлемая черта других языков, и до сегодняшнего времени ее не хватало в Swift. Не будем дискутировать о достоинствах try/catch , давайте лишь рассмотрим как это работает и что вы можете с этим сделать, если установили Xcode 7.

try/catch  - это способ программирования, который означает «попробуйте это и, если не получится, то используйте другое взамен». Swift использует перечисление для типов ошибок, и вы можете быть уверены, что ваша ошибка будет определена, также как с оператором switch. Таким образом, вы можете увидеть список ошибок, примерно в таком виде:

enum MyError: ErrorType {
    case UserError
    case NetworkError
    case DiscoverydError
}

Обратите внимание на то, как мой тип ошибки встраивается во встроенный протокол ErrorType - это необходимое требование.

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

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

func doStuff() throws -> String {

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

func doStuff() throws -> String {
    print("Do stuff 1")
    print("Do stuff 2")
    throw MyError.NetworkError
    return "Some return value"
}

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

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

try doStuff()

Буквально, мы пишем в коде что «признаем возможность несрабатывание кода» и это позволяет обеспечить нам дополнительную безопасность. Но, даже с этим ваш код все еще не будет компилироваться, потому что мы не ловим ошибки. Для того, чтобы это исправить вам нужно использовать do и catch.

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

Вот общий пример того, как происходит выявление всех ошибок:

do {
    try doStuff()
    print("Success")
} catch {
    print("An error occurred.")
}

Если вы помните, мы применяли doStuff() метод когда писали “Do stuff 1”, затем “Do stuff 2 перед тем, как выбрасывать ошибку сети. Вот что получится:

  1. Будет напечатано “Do stuff 1”
  2. Будет напечатано “Do stuff 2”
  3. Появится ошибка NetworkError, затем незамедлительно произойдет выход из метода doStuff(), другими словами, исполнение кода никогда не дойдет до оператора возврата.
  4. Исполнение перескочит на блок catch
  5. Будет напечатано "An error occurred"

Чтобы было понятно: в коде выше, "Success" не будет написано до тех пор, пока любой из try методов выбрасывает ошибку, происходит остановка выполнения и перескок к блоку catch.

Как я уже говорил, можно смешивать и сочетать общие и специфические catch блоки, но вам нужно быть уверенным, что все типы ошибок будут пойманы. Например, этот кусок кода проверит на наличие ошибок NetworkError, а другой кусок на наличие других других ошибок.

do {
    try doStuff()
    print("Success")
} catch MyError.NetworkError {
    print("A network error occurred")
} catch {
    print("An error occurred")
}

Автоматически синтезируемые заголовки

Это небольшое, но поверьте очень долгожданное изменение. Чтобы задействовать его в Xcode 7, вам нужно перейти Navigate > Generated Interface.

В Objective-C .h файлы предоставляют список функционала классов без его реализации.

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

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

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

Ключевое слово “guard”

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

func submitTapped() {
    guard username.text.characters.count > 0 else {
        return
    }
    print("All good")
}

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

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

guard let unwrappedName = userName else {
        return
}
print("Your username is \(unwrappedName)")

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

if let unwrappedName = userName {
print("Your username is \(unwrappedName)")
} else {
        return
}
// это не будет работать, так как извлеченного значения больше не существует
print("Your username is \(unwrappedName)")

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

Если вы читали username.text.characters.count, проделывая двойную работу, не вините себя: Apple вновь изменила подсчет количества символов в строке. То, что было countElements(), стало count(), а теперь и совсем исчезло. По факту, если вы захотите использовать count() в строке, то выйдет ошибка.

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

Ключевое слово “defer”

В некоторых языках существует try/finally, который позволяет сказать вашему приложению что «этот код должен быть выполнен не зависимо от обстоятельств». В Swift 2 появляется ключевое слово defer как решение этой задачи: это означает «я хочу чтобы работа была сделана, но несейчас». На практике это обычно означает, что работа будет сделана, до того, как закончится ваш метод, но фишка в том, что она будет закончена и в том случае, если вы выбросите ошибку.

Сначала, рассмотрим простой пример:

override func viewDidLoad() {
    super.viewDidLoad()

    print("Checkpoint 1")
    doStuff()
    print("Checkpoint 4")
}

func doStuff() {
    print("Checkpoint 2")
    defer { print("Do clean up here") }
    print("Checkpoint 3")
}

Если вы это запустите, то увидите "Checkpoint 1", "Checkpoint 2", "Checkpoint 3", "Do clean up here", затем "Checkpoint 4". И даже факт того, что defer появился до checkpoint 3, исполнение этого defer будет отложено до того момента, пока метод уже вот-вот будет считаться исполненным.

Я располагаю "Do clean up code here" в этой части именно потому, что defer  как раз хорош в этом: когда вы знаете, что нужно будет очистить кэш, выписать файл или что-то еще, и вы хотите быть уверены, что ваш код в любом случае будет выполнен, независимо от того, по какому пути вашего метода это произойдет.

Например:

override func viewDidLoad() {
    super.viewDidLoad()
    print("Checkpoint 1")
    do {
        try doStuff()
    } catch {
        print("Error!")
    }
    print("Checkpoint 4")
}
func doStuff() throws {
    print("Checkpoint 2")
    defer { print("Do clean up here") }
    throw MyError.UserError
    print("Checkpoint 3")
}

До тех пор, пока doStuff() выбрасывает ошибку, метод закрывается и на этом этапе вызывается отложенный код.

Предупреждения о неизменчивости.

Это простое изменение, которое имеет большое будущее и направлено на помощь в чтении кода. Как вы знаете, разработчики на языке Swift предпочитают называть вещи константами (используя let), чаще чем переменными (используя var). А что если вы назвали что-то переменной по ошибке? Или вы думали, что вам нужно будет это поменять, но не сделали этого?

В Xcode 7 и Swift 2, вы будете получать предупреждения о том, что используете переменные, которые не изменяются. Xcode буквально изучает то, как вы используете переменные и знает, будете ли вы их менять.

Проверка доступности API

Существует одна проблема с которой столкнулись iOS разработчики - это необходимость быть осторожным при использовании новых API. Если вы попробуете использовать UIStackView на iOS8, то ваше приложение упадет. В прошлом, разработчики на Objective-C написали бы код таким образом:

NSClassFromString(@"UIAlertController") != nil

Что означало бы: "если класс UIAlertController существует" и это было способом проверки, если вы работали на iOS 8 или более поздней версии. Но, так как Xcode не знал, что было нашей целью, то он не мог понять, все ли мы делаем правильно. В Swift 2 это устранено и теперь, вы можете написать код таким образом:

if #available(iOS 9, *) {
    let stackView = UIStackView()
    // do stuff
}

Магия будет происходить с #available: он будет автоматически проверять работаем ли мы на iOS 9 или более поздней версии, и, если да, то будет писать код с UIStackView. Ставя  * после "iOS 9" мы указываем, что будут использованы любые будущие платформы Apple.

Итак, #available это отличная особенность, но она может быть и лучше, если вы используете для нее блок else. Так как Xcode теперь знает, что только его нужно использовать для устройств на iOS 8 и более ранних, то он предупредит вас, если вы используете новые API. Например, вы написали что-то подобное:

if #available(iOS 9, *) {
    // do cool iOS 9 stuff
} else {
    let stackView = UIStackView()
}

Вы не сможете этого создать: Xcode увидит, что вы пытаетесь использовать UIStackView там, где это не доступно, и просто не позволит этому произойти. И так, просто переходя от "is this class available" к тому, что мы проговариваем Xcode, наши фактические намерения, что создает нам большую подушку безопасности.

Оригинал: http://www.hackingwithswift.com/swift2