Вышла версия Swift 1.2 : долгожданные изменения.

Screen Shot 2015-04-12 at 10.28.29 AM

;

 

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

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

— улучшенные сообщения об ошибках и повышение стабильности в Xcode

— “static” методы и свойства ( properties) теперь разрешены в classes (как псевдоним для “class final”). Теперь вам разрешается декларировать как «static» хранимые свойства (stored properties) в  classes, которые сохраняются глобально и  lazily инициализируются при первом доступе (то есть как глобальные переменные).

Screen Shot 2015-04-13 at 4.00.39 PM
Теперь очень легко создавать Singletons — буквально одной строкой кода.

Screen Shot 2015-04-18 at 5.13.34 PM

— в протоколах (Protocols) теперь требуется декларировать тип как “static” вместо “class

— появился новый тип  Set, который является взаимозаменяемым (bridging) с Objective-C классом  NSSet, также, как и  String, Array и Dictionary — взаимозаменяемы ( bridging) с их соответствующими  Objective-C классами. Вы можете делать с Set в Swift все, что вы ожидаете делать с множествами вообще: проверять принадлежность значения к множеству, перечислять все элементы множества, выполнять операции union и intersect и т.д. Это существенное дополнения в стандартную библиотеку, которое закрыло огромную абстрактную «дыру», так как   NSSet не чувствовал себя комфортно в Swift

Screen Shot 2015-04-12 at 4.45.14 PM
Screen Shot 2015-04-12 at 4.52.51 PMЖдем того же от  NSDate и других.

— изменен глобальный метод countElements на count

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

Screen Shot 2015-04-13 at 4.03.09 PM

— добавлена глобальная функция zip(), которая объединяет две последовательности в одну последовательность кортежей (tuple)

Screen Shot 2015-04-13 at 4.10.53 PM

— Swift enums теперь могут экспортироваться в Objective-C , используя @objc атрибут.

Swift код:

[objc]
@objc enum Bear: Int {
Black, Grizzly, Polar
}
[/objc]

импортируется в Objective-C как:

[objc]
typedef NS_ENUM (NSInteger, Bear){
BearBlack, BearGrizzly, BearPolar
};
[/objc]

— улучшилось использование  C union, битовых полей и других данных, не являющихся «родными» для Swift

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

Screen Shot 2015-04-10 at 12.35.04 PM
В Swift 1.2 должно быть так

Screen Shot 2015-04-10 at 12.39.21 PM

Это возможный шаблон, в который можно «заворачивать» let свойства для упрощения их семантики в инициализаторах:

let x : SomeThing
if condition {
	x = foo()
} else {
	x = bar()
}
use(x)

— в Swift 1.2, «кастинг» вниз (subclass кастинг) можно выполнять либо как Optional с as?, либо как  “принудительный” с as!. Если вы уверены относительно типа, то можете его усилить с помощью  as! подобно тому как мы используем «неявно развернутые Optionals ( implicitly-unwrapped Optional), и «ловить» ошибку на этапе компиляции, а не на этапе runtime.

Но самое большое изменение касается if let Optional привязки (binding). Наконец-то!!. Конструкция  if let раньше использовалась для условного «развертывания» Optional значений. В прошлом вы могли «развернуть» только одно значение за раз, что приводило к так называемой “пирамиде смерти” if let  блоков, например такой:

Screen Shot 2015-04-09 at 6.52.31 PM

Теперь это выглядит так

Screen Shot 2015-04-09 at 6.58.31 PM
Порядок выполнения операций в этих двух примерах совершенно идентичен. Используя новый синтаксис, каждая привязка (binding) оценивается в порядке очереди, останавливаясь, если любая из попыток повязки приводит к nil. Только после того, как Optional привязки успешно выполнены, проверяется предложение  where.

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

Вот как может выглядеть канонический пример JSON парсинга большого блока JSON данных в Swift 1.2. Пример использует один if let блок для обработки Optionals, которые получаются с использование  NSBundle, NSURL и NSData, затем вступает в силу другой  if let блок для JSON интерпретации нескольких экземпляров  AnyObject? как специфических типов:

Screen Shot 2015-04-09 at 8.05.39 PM

Нет необходимости представлять Модель  User  c использованием операторов функционального программирования и каррирования

Screen Shot 2015-04-10 at 11.04.13 PM

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

Продолжая тему новых возможностей Swift 1.2, которые помогают нам обрабатывать  Optionals: Swift 1.2 представляет flatMap. Это  map операция, за которой следует операция «выпрямления» (flattening).

Существующая map позволяет вам применять функцию к значению внутри Optional, если это Optional не nil. Например, представим, что у нас есть Optional integer i и мы хотим удвоить ее. Вы могли бы написать i.map { $0 * 2 }. Если i имеет значение, вы получите назад Optional удвоенного значения. С другой стороны, если i это nil, то дублирования не произойдет.

Теперь представим, что вместо удвоения целого числа, вам нужно выполнить  map над Optional, которое само по себе возвращает Optional. В документации представлен пример с глобальной функцией find, которая ищет в массиве заданное значение и возвращает его индекс, если значение найдено, или  nil, если оно не найдено:

Screen Shot 2015-04-10 at 11.03.55 AM

Озабоченность вызывает то. что пап idx будет Int??, потому что  fst уже является Optional, и затем мы применяем map к find, который также возвращает Optional – теперь у нас Optional, «завернутое» в Optional.

Screen Shot 2015-04-10 at 11.12.02 AM

Мы хотим «выпрямить» (“flatten”) это вложенное  Optional –  поэтому вместо map мы используем flatMap:

Screen Shot 2015-04-10 at 11.15.53 AM

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

Screen Shot 2015-04-10 at 12.01.30 PM

То же самое можно получить другим путем, если использовать цепочку Optional  для first метода для массивов:
Screen Shot 2015-04-10 at 12.08.15 PMВ этом месте становится понятным, что flatMap и цепочки Optional делают одну и ту же работу. По существу, цепочки Optional — это компактная версия flatMap,  которая работает только на методах. Если вам нужна такая же функциональность для функций, в которые передается Optional значение  (как find), то нужно использовать метод flatMap.

Но это не все относительно flatMap. Она является методом массивов (если думать о функции map для массивов, как о превращении всех элементов всех массивов в один массив элементов ), и вообще есть  generic версия глобальной функции flatMap, которая  работает с последовательностями (sequences) и коллекциями  (collections). Есть море статей на эту тему, но лучшее объяснение с примерами дано в статьях Alexandros Salazar.

Внесены изменения в столь удобную и автоматическую  взаимозаменяемость (bridging) Objective-C классов и соответствующие им Swift типы значений. Неявное преобразование из взаимозаменяемых ( bridged ) Objective-C классов (NSString/NSArray/NSDictionary) в соответствующие им Swift типы значений (String/Array/Dictionary) удалено, сделав систему типов в Swift проще и более предсказуемой, как утверждает Apple. Сюда же относится и пара NSSet в Objective-C и вновь испеченный Set в Swift. Для того, чтобы осуществить такое взаимозаменяемое преобразование, нужно выполнить это преобразование вручную с помощью операторов c ключевым словом as. Взаимозаменяемость в обратном направлении из Swift типы значений (String/Array/Dictionary) в соответствующие ( bridged ) Objective-C классы (NSString/NSArray/NSDictionary) работает автоматически.

Swift становится гражданином «первого класса»: Swift типы будут работать везде, где ожидается работа их или  Objective-C аналогов. Вам придется сделать изменения в своем приложении в Swift 1.2, если вы выработаете  с экземплярами старых Objective-C классов.

Автоматическая взаимозаменяемость (briging) в направлении NSString -> String не работает в Swift 1.2 автоматически и требует «кастинга»с помощью as, as?, as!

Screen Shot 2015-04-10 at 3.06.22 PM

 Нужно выполнить «кастинг» вручную, но будьте внимательны: автоматическое исправление ошибок будет предлагать вам as, но сигнатура метода требует as?

Screen Shot 2015-04-10 at 3.07.58 PM

Автоматическая взаимозаменяемость (briging) в направлении String -> NSString сохранена и работает  в Swift 1.2.

Хочу отметить, что изменилась терминология: вместо объектов (objects) в Objective-C, в Swift используются значения (values), вместо классов ( classes) в в Objective-C, в Swift используются типы (types).

Сигнатуры методов стали  более адаптированы к Swift.

Например, раньше был метод

[js]
func touchesBegan(touches: NSSet, withEvent event: UIEvent)
[/js]

Теперь

[js]
func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
[/js]

Это шаг в правильном направлении, но лучше было бы Set <UITouch>.

Новый @noescape атрибут для замыканий

Атрибут @noescape может быть использован в замыканиях для того, чтобы показать, что замыкание «не переживет» «времени жизни» вызова (call). Если более простыми словами, то замыкание с атрибутом @noescape  показывает, что замыкание будет удалено (be released), когда метод закончит работу. Если вы используете этот атрибут, то вам нет необходимости при ссылке на переменные экземпляра класса использовать ключевое слово self. Это делает контекст и использование замыкания прозрачным для пользователя этого замыкания. Вдобавок, согласно Apple, это способствует оптимизации производительности.

Рассмотрим использование @noescape . Вам достаточно добавить @noescape перед именем параметра

Screen Shot 2015-04-13 at 3.17.12 PM

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

Screen Shot 2015-04-13 at 3.27.01 PM

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

Nullability аннотация типов аргументов, переменных и свойств в Objective-C

Nullability означает способность принимать null значения. Это соответствует концепции Optionality в Swift. Nullability может быть выражена специальными квалификаторами Objective-C типа, и имеет огромное значение для значительного количества классов в стандартной библиотеке и для сигнатуры функций.

В Swift есть четкое различие между Optional и не-Optional ссылками, например, NSView и NSView?, в то время как Objective-C представляет оба этих типа как NSView *. Так как компилятор Swift не может быть уверен наверняка, является ли данный NSView * Optional или нет, то в Swift интерпретирует этот тип как неявное «развернутое» Optional (implicitly unwrapped Optional), NSView!.

В предыдущих релизах сама Apple снабдила свои frameworks правильными Swift  Optionals. Xcode 6.3 поддерживает эту возможность для вашего кода путем добавления в Objective-C новой возможности: Nullability аннотаций.

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

Если вы продолжаете писать на Objective-C, то можете использовать некоторые новые квалификаторы для аргументов, переменных и свойств:

  • nonnull – никогда не бывает nil
  • nullable – может быть nil
  • null_unspecified – неизвестно, может быть nil или нет (в настоящий момент используется по умолчанию)

Рассмотрим соответствие между Objective-C декларированием и соответствующими  Swift типами:

  • nonnull NSString *string – обычный String
  • nullable NSString *string – Optional String?
  • null_unspecified NSString *string – неизвестно, является неявно «развернутым» ( implicitly unwrapped) String!

Рассмотрим Nullability в действии (пример взят из Swift 1.2)

Давайте посмотрим на API некоторого LocationDataСontroller, используемого для управления списком местоположений, locations. К каждому местоположению подключается фотография photo:

Screen Shot 2015-04-10 at 8.39.01 PM

Без Nullability анотаций, каждый указатель в моем  LocationDataController классе импортируется в Swift как неявно «развернутое» Optional (implicitly unwrapped Optional):

Screen Shot 2015-04-10 at 8.30.06 PM

Вот как я могу аннотировать Objective-C API моего класса:

Screen Shot 2015-04-10 at 8.42.51 PM

Во-первых, свойства — мой список местоположений locations является nonnull, так как в худшем случае, это будет пустой массив, но latestLocation может быть nil если в списке locations ничего пока нет. Параметры в моих двух методах должны всегда иметь значения, но из-за того, что не все locations имеют photo, то второй метод возвращает nullable photo.

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

Screen Shot 2015-04-10 at 8.26.12 PM

NSEnumerator.generate() -> NSFastGenerator

Screen Shot 2015-04-12 at 5.17.04 PM

Screen Shot 2015-04-12 at 5.17.50 PM

Перегрузка (overloading) функций в Swift 1.2 в Objective-C совместимых классах

Эта проблема очень подробно описана в посте «Дополнение к Лекция 2 — особенности кода Calculator в Swift 1.2 или как не «застрять» на этом месте».

Миграция на Swift 1.2

Apple обеспечила меню для перехода на версию 1.2. Это работает, но не всегда результат является подходящим.

Меню Xcode > Edit > Convert > To Swift 1.2

Есть «подводные камни», связанные с преобразованием  NSSet в Set<NSObject> или  NSString! в String.