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

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

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

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


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


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



map, filter и компания


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

// 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 }

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

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

// 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

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

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


В Swift 2, enum обладают достаточной reflection информацией, чтобы сделать возможной их печать.



Предложение print (an) теперь будет корректно печатать Dragon, хотя в предыдущей версии Swift вывод был совершенно не информативным (Enum Value).

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



Теперь enum могут быть рекурсивными, то есть мы можем построить дерево с помощью enum. Давайте посмотрим на этот пример:



Мы должны использовать ключевое слово indirect впереди case Node. И это позволило нам создать дерево:



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



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



Должен быть напечатан результат, равный 21.

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


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

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

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


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

viewAnimationOptions = nil
viewAnimationOptions = .Repeat | .CurveEaseIn | .TransitionCurlUp
if viewAnimationOptions & .TransitionCurlUp != nil { ...

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



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



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

Следует заметить, что множество опций OptionSetType теперь полагается на другую возможность в Swift 2, называемую «умолчательной» реализацией расширений протокола (default implementations for protocol extensions), так что просто подтверждая протокол OptionSetType, вы получаете реализацию по умолчанию, например, для метода contains, subtractInPlace, unionInPlace и других операций над множествами. Мы рассмотрим расширения протокола позже.

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


Синтаксис 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, вышеприведенный код будет выглядеть так:



Так что функции получили то же самое соглашение, что и методы:

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

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

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

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


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



Для того, чтобы избежать неоднозначности с предложением 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 код в специальном «тестируемом» режиме



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



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

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

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


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

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


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

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

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

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

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

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

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

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



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



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



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

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

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

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



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

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

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

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

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

Repeat — while


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



Есть две причины для таких изменений:

Когда вы используете 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 внутри предложений ifguard). Давайте сначала определим простейшее перечисление Number, а затем покажем способы его применения.



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


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



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

Вот эквивалентная Swift 1.2 версия, использующая switch:



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


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



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

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



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


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



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




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




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. В этом случае наши намерения состоят в том, чтобы пройтись по элементам последовательности, но только по тем, которые соответствуют заданному «образцу». Вот пример:



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

Pattern matching while


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



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

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


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

var optional1: String?
var optional2: String?
  
if let optional1 = optional1, let optional2 = optional2 {
    print("Success")
} else {
    print("Failure")
}

Здорово!

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

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")
}

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

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



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

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


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

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

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

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

//Локальная переменная error запоминает ошибку, если она возвращается
var error: NSError?
// success это Bool:
let success = someString.writeToURL(someURL,
                                    atomically: true,
                                    encoding: NSUTF8StringEncoding,
                                    error: &error)
if !success {
    // Выводится информация об ошибке error:
    println("Error writing to URL: \(error!)")
}

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

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

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



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



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



Теперь эта функция может выбрасывать ошибку, используя ключевое слово throw и ссылку на конкретный тип ошибки:


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



Потому что функция объявила, что она способна выбрасывать ошибки, и вы должны «ловить» потенциальные ошибки. Давайте попробуем использовать ключевое слово try:



Этого оказалось недостаточно, компилятор сообщает нам, что требуется обработка ошибок, которая производится с помощью синтаксической конструкции do-try-catch:


Более того, в блоке do-try-catch у вас есть возможность «ловить» несколько ошибок:



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



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

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

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

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



Перейдем к рассмотрению реального примера, представленного в статье Natasha Murashev. Swift 2.0: Let’s try?. Рассмотрим данные, которые приходят с некоторого API (после десериализации):



Эти данные нужно преобразовать в Модель для последующего использования в приложении:



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



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



Выполним парсинг «плохих» данных.



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



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

— фундаментальные конструкции языка, такие, как enum, scoping (область действия), синтаксис аргументов и т.д.
— сопоставление с образцом (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

Комментарии (36)


  1. chiliec
    22.10.2015 13:16
    +2

    Спасибо за статью, отличные примеры! Вроде всё это и так знал, но всё равно такое ощущение что узнал что-то новое.


  1. Athari
    22.10.2015 21:12
    +2

    Мимо проходил. Количество ломающих изменений такое, будто переход не от 1.2 к 2.0, а от 0.5 к 0.6. Переписывать же невесело будет…


    1. WildGreyPlus
      22.10.2015 21:18
      +2

      Я не очень поняла ваше замечание. От 0.5 к 0.6 — это больше или меньше 1.2 от 2.0? Но, если по делу, то переходить очень легко — работает автоматическая миграция от Swift 1.2 к Swift 2.0 и работает очень хорошо, даже для больших проектов — все попробовала. Остаются только мелочи, которые легко поправить, если знаешь, как это должно быть.


      1. Athari
        22.10.2015 21:34
        +1

        В смысле сплошником радикальные изменения синтаксиса: не просто добавление ключевых слов или изменение обработки каких-то случаев, а полное изменение назначения ключевых слов, изменение синтаксиса вызова всех функций и тому подобное. Такие изменения характерны на ранних стадиях развития до первого релиза 1.0, а потом обычно стараются не ломать всё и сразу. Если свой проект можно прогнать через конвертировалку, то как быть, например, разработчикам библиотек, которые поддерживают несколько версий? Да и как в обычном проекте быть, если главная ветка перешла на новую версию, а старые ветки тоже надо поддерживать и мержить изменения? Короче, странно всё это видеть.


        1. WildGreyPlus
          22.10.2015 21:49
          +2

          Проходя мимо, вы, возможно, не в курсе, что на Swift 1.x вам никто и не предлагал разрабатывать промышленные библиотеки, хотя смельчаки, конечно, находились, потому что это совершенно новый язык и никто не знал, куда он «вывернет». Были прогнозы, что он вообще уйдет в сторону функционального программирования. Но прошел год, когда весь мир участвовал в отладке Swift, и сейчас мы получили не только новый язык Swift 2, но и новые концепции программирования — помимо Объектно-Ориентированного Программирования (OOП), которое было там изначально, помимо элементов Функционального Программирования, еще и Протоколо-Ориентированное Программирование. Кроме того язык очень краткий и очень красивый. Программирование на нем — очень увлекательное занятие. Так что странного ничего нет. Пробуйте. Я думаю он и вас не оставит равнодушным.


          1. Athari
            22.10.2015 21:56
            +5

            А, понятно, «1.0» по смыслу был «0.5», а «2.0» — что-то типа «0.9». Тогда всё встаёт на свои места. :) Традиции расстановки цифр в версиях разные, да и цифры конкурентов догонять надо… Вообще, я сторонник идей semver.org.


  1. deniskreshikhin
    22.10.2015 21:22
    +1

    Хороша статья!

    Жаль, что на обещание сделать его Open Source они видимо забили.

    Swift source code will be released under an OSI-approved permissive license.
    Contributions from the community will be accepted — and encouraged.
    At launch we intend to contribute ports for OS X, iOS, and Linux.
    Source code will include the Swift compiler and standard library.
    We think it would be amazing for Swift to be on all your favorite platforms.


    developer.apple.com/swift/blog/?id=29

    Хотелось бы заиметь такое под Linux.


    1. WildGreyPlus
      22.10.2015 21:29
      +2

      Почему забили? Нет, они обещали к концу 2015 года. Все будет. Они очень заинтересованы в продвижении Swift. По-моему даже Windows будут что-то реализовывать у себя для разработки приложений на Swift. Если вы заметили, то Microsoft на последнем Keynote первыми демонстрировали разработку Microsoft Office для iOS. Это необычно.


      1. deniskreshikhin
        22.10.2015 21:32
        +1

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

        Так что, подождем 2 месяца)


      1. xGromMx
        24.10.2015 03:46

        Что-то похожее уже есть) www.elementscompiler.com/elements/silver


    1. Athari
      22.10.2015 21:43

      А какие у свифта киллер-фичи кроме совместимости с Obj-C?


      1. WildGreyPlus
        22.10.2015 22:01
        +1

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


      1. deniskreshikhin
        22.10.2015 22:12

        Он не особо-то и совместим с ObjC, т.к. нельзя их код перемешивать. Т.е. нет той совместимости как у Objective-C и C, C++.

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


        1. WildGreyPlus
          22.10.2015 22:55
          +2

          Почему нельзя перемешивать? Пожалуйста, можете использовать любые Objective-C классы в Swift проектах, для облегчения доступа к ним в Xcode 7 добавлены 3 новые возможности для Objective-C:
          — nullability;
          -lightweight generics;
          __kindof types.
          И, наоборот, можете выставить свой Swift класс для использования в Objective-C.
          В Swift 2 сильно улучшено взаимодействие с С-функциями и теперь указатели на C-функции — это просто специального вида замыкания, аннотируемые атрибутом @convention.
          Я хочу об этом писать во второй части.


          1. deniskreshikhin
            23.10.2015 00:37
            +1

            Я имел ввиду что-то подобное этому:

            #import <Foundation/Foundation.h>
            
            class Hello {
                private:
                    id greeting_text;  // holds an NSString
                public:
                    Hello() {
                        greeting_text = @"Hello, world!";
                    }
            
                    Hello(const char* initial_greeting_text) {
                        greeting_text = [[NSString alloc] initWithUTF8String:initial_greeting_text];
                    }
            
                    void say_hello() {
                        printf("%s\n", [greeting_text UTF8String]);
                    }
            };
            


        1. EvilPartisan
          23.10.2015 00:58

          Возможность подобного смешивания наложила бы существенные ограничения на сам язык и потянуло бы соответствующие недостатки из старого языка. Всё-таки Objective-С это надстройка над C. А Swift — это именно другой язык с другой внутренней архитектурой. Совместимость такого рода как реализована сейчас, на мой взгляд, является самой оптимальной. У вас есть возможность использовать языки разного уровня и типа в одном проекте и достаточно прозрачно их сшивать — и это уже очень здорово!
          Да и вообще плохой это тон — мешать код разных языков в одном файле.


          1. deniskreshikhin
            23.10.2015 01:22

            Я и не оспариваю что это так)

            Просто, в контексте Objective-C слово совместимость (compatibility) означало, что код на C/C++ можно было вставлять прямо куда хочешь, без переделки. Этой совместимости на уровня кода в Swift нет.

            Зато, есть совместимость на уровне интерфейсов (interoperability). Но это трудно назвать a killer feature, т.к. это явление довольно рядовое на современных платформах. В .Net совместимость между С++/C#/VB появилась еще 15 лет назад. Кроме этого многие языки сами по себе поддерживают хороший FFI с C/C++. Golang, Rust тому пример.


            1. EvilPartisan
              23.10.2015 01:38

              Разумеется это не киллер-фича, но это критически необходимая вещь для выживания любого нового языка.
              Вообще в индустрии так сложилось, что каждый крупный (да и не очень крупный) игрок старается или вынужден разрабатывать свой собственный язык. И Apple просто сделала очередной необходимый шаг. Можно обожать ObjC сколько угодно, но давайте по-честному всё-таки он уже устарел, так что Apple даже несколько затянула с этим и вынуждена сейчас форсировать события. Поэтому мы наблюдаем такие частые и сильные изменения в языке.
              Как бы то ни было, лично я считаю, что Swift может и не лучше других модных ныне языков (Go, C#, Rust и тд) но по крайней мере не хуже и благодаря поддержке Apple имеет хорошие шансы занять существенную долю рынка даже за пределами инфраструктуры Apple.


            1. egyp7
              23.10.2015 15:38
              +1

              А вот это вы зря! Только что поставил и погонял Beta релиз Xcode 7.1 (Build: 7B91b) с поддержкой Swift 2.1.
              В версии 2.1 добавили возможность использования си кода без всяких бриджей (наконец то избавились от костылей что юзали раньше) :)

              func xyz() throws {
                 let f = fopen("x.txt", "r")
                 defer { fclose(f) }
                 try foo(f)                    // f is closed if an error is propagated.
                 let f2 = fopen("y.txt", "r")
                 defer { fclose(f2) }
                 try bar(f, f2)                // f2 is closed, then f is closed if an error is propagated.
              }                                // f2 is closed, then f is closed on a normal path
              


              Более подробно можно почитать тут: https://developer.apple.com/library/prerelease/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc7_release_notes.html

              Apple делает правильные шаги для того, чтобы заменить Objective C на более перспективный и мощный язык программирования; и как мне кажется делает это весьма успешно ;)


              1. deniskreshikhin
                23.10.2015 15:50

                Добавили возможность делать вызовы. Это не то же самое что вставлять участки кода.

                Т.е. такое не покатит:

                func xyz() throws {
                   FILE* f = fopen("x.txt", "r");
                   fclose(f);
                } 
                


                1. egyp7
                  23.10.2015 16:54
                  +1

                  ну так унификация типов же.
                  и ведь let f = fopen(«x.txt», «r») намного лучше выглядит чем FILE* f = fopen(«x.txt», «r»);
                  алсо никто не мешает определять типы вручную:

                  let f:UnsafeMutablePointer<FILE> = fopen("x.txt", "r");
                  


                  1. deniskreshikhin
                    23.10.2015 17:06

                    Да много расхождений не только в типах, по другому сделаны switch, do-while и т.д.


              1. EvilPartisan
                24.10.2015 18:20

                Это не си код, это как раз самый обычный swift-код.


                1. WildGreyPlus
                  24.10.2015 18:41

                  Конечно, это Swift код, но обращение к С-функциям напрямую, без всяких UnsafeMutablePointer.
                  Вот еще один пример — вызываем функцию сортировки qsort из библиотеки strdlib.h


                  хотя сигнатура у нее очень накрученная


                  1. EvilPartisan
                    24.10.2015 18:54

                    Предыдущая версия свифт вела себя также, «без всяких UnsafeMutablePointer», разве что try и defer еще не было.
                    Я не понимаю о чем вы спорите, человек хотел вставлять си код, внутрь кода Свифт, вроде того как работают ассембленые вставки в си. Это невозможно сделать чисто технически, а вызывать сишные функции можно было изначально.


                    1. EvilPartisan
                      24.10.2015 19:08

                      Даже не как вставки, а прям всплошную, что вообще безумие. (0_о)


  1. vba
    23.10.2015 09:52

    Спасибо за обзор.

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

    case let (.Some(username), .Some(password)):
        print("Success!")
    

    Скажите пожалуйста а здесь .Some это своего рода вызов статического метода из предварительно импортированного пространства имен.
    Типа статического импорта в Java или C#?


    1. WildGreyPlus
      23.10.2015 10:51

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

      enum Optional {
          case None
          case Some(T)
      }
      

      Когда вы задаете Optional значение, то добавляете к типу знак вопроса ?
       var username: String?
       var password: String?
       
      username = .Some("Alex")
      password = .None
      

      Но как только вы это совершили, вы будете обращаться со своими переменными username и password как перечислениями — они оказались «завернутыми» в Optional тип.
      Для извлечения ассоциированных значений (String, а не String? ) используется оператор switch



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


      В результате вы общаетесь только со своим идентификатор username и nil и знать ничего не знаете о .Some и .None.


      1. vba
        23.10.2015 11:24

        Да это я все понял, спасибо. Вопрос был немного о другом, что именно означает точка перед .Some это логически должно быть эквивалентно Optional.Some. Просто мне, человеку с багажом Java/C# точка бьет в глаз, вот и мучаю себя вопросом эквивалентно ли это статическим импортам.


        1. WildGreyPlus
          23.10.2015 11:28

          Точка означает обращение к перечислению. Да это эквивалентно Optional.Some. Как к нему обратиться в Java?


        1. WildGreyPlus
          23.10.2015 11:34

          Вы же в switch указали переменную, по которой будете «переключаться»? Компилятору уже известно, что она Optional. Осталось только указать значения перечисления (с возможностью извлечения ассоциированных значений). И это указывается с применением точки.


  1. DmitrySpb79
    23.10.2015 12:00

    Спасибо за статью, интересно. Пишу на Swift, но скорее в С/ObjC-стиле, так что узнал много нового.

    Вообще, что в ObjC что в Swift, огорчает что это уж больно нишевые языки, которые практически не имеют хождения за пределами экосистемы Apple (но видимо это вечная участь iOS-разработчиков, увы). Уж лучше бы в Apple вторым языком яву выбрали :)))


    1. EvilPartisan
      23.10.2015 12:40

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


    1. WildGreyPlus
      23.10.2015 12:43

      Если хотите писать приложения в соответствии с логикой Swift, то полезно посмотреть русский перевод стэнфордских лекций «Developing iOS 8 Apps with Swift» на сайте «Разработка iOS + Swift + Objective-C». Там профессор учит писать приложения именно в соответствии с логикой Swift (Наблюдатели Свойств, вычисляемые свойства, отложенная инициализация и т.д.).
      По поводу «нищевости» — увидим, по крайней мере Apple сейчас предпринимает огромные усилия, чтобы выйти из этой ниши. Даже разработала методику обучения Swift в школах. Кстати, по своим возможностям Swift превзошел Java, хотя бы в плане функционального программирования.


  1. fiveze
    23.10.2015 14:06

    Это делает guard естественным способом проверки нефатальных предварительных условий без использования «пирамиды сметри»

    А что за «пирамиды сметри»? Ничего не нагуглил по этому термину.


    1. WildGreyPlus
      23.10.2015 14:24

      Если вам нужно парсить JSON данные

      {
        "stat" : "ok",
        "blogs" : {
          "blog" : [
            {
              "needspassword" : true,
              "id" : 73,
              "name" : "Bloxus test",
              "url" : "http:\/\/remote.bloxus.com\/"
            },
            {
              "id" : 74,
              "name" : "Manila Test",
              "needspassword" : false,
              "url" : "http:\/\/flickrtest1.userland.com\/"
            }
          ]
        }
      }
      


      в модель

      struct Blog { 
        let id: Int 
        let name: String 
        let needsPassword : Bool 
        let url: NSURL
      }
      


      то в строго типизированном языке, как Swift, получается множество вложенных if
      func parseBlog(blogDict: [String:AnyObject]) -> Blog? { 
        if let id = blogDict["id"] as NSNumber? { 
          if let name = blogDict["name"] as NSString? { 
            if let needsPassword = blogDict["needspassword"] as NSNumber? { 
              if let url = blogDict["url"] as NSString? { 
                return Blog(id: id.integerValue,
                            name: name,
                            needsPassword: needsPassword.boolValue,
                            url: NSURL(string: url)
                            )
              }
            }
          }
        }
        return nil
      }
      


      Это и называется «pyramid of doom» (пирамида смерти), она характерна не только для Swift. В Google надо искать «pyramid of doom».