Swift 3.1 – это небольшое и полностью совместимое с предыдущими версиями обновление
Swift 3.0, задачей которого является доработка основных преимуществ, в рамках подготовки к более серьезным изменениям, которые появятся в Swift 4.0, выхода которого мы ждем в июне. Была проделана большая подготовительная работа для этих изменений (детали можно посмотреть здесь и здесь ), результатом которой стали несколько дополнительных функций, которые смогут уже в скором времени оценить разработчики.
В этой статье я собираюсь продемонстрировать три наиболее полезных изменения с примерами в коде, и, я надеюсь, что это даст вам возможность подготовиться к грядущим изменениям и обновить свой код, когда они настанут.
Заметка
Несмотря на то, что это не является ключевым моментом для Swift 3.1, я не могу опубликовать эту статью, не упомянув, что Xcode 8.3 перестает поддерживать Swift 2.3. Итак, если вы все еще не решались перейти на Swift 3.x, то теперь настало время это сделать!
Если вам понравится данная статья, то возможно вам так же понравятся и книги на :
- What's new in Swift 3? (англ.)
- Swift tutorial series (англ.)
- Server-Side Swift book (англ.)
- Hacking with macOS book (англ.)
Расширение типов через ограничения
Swift позволяет нам расширять типы через использование ограничений, что является мощным и выразительным способом добавления функциональности. Чтобы продемонстрировать это, давайте рассмотрим пример с решением на Swift 3.0, который модифицирует коллекции для тривиальной задачи:
extension Collection where Iterator.Element: Comparable {
func lessThanFirst() -> [Iterator.Element] {
guard let first = self.first else { return [] }
return self.filter { $0 < first }
}
}
let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)
Это добавляет новый метод lessThanFirst(), который возвращает все те элементы коллекции, которые меньше первого элемента. Таким образом, использование его в массиве [5, 6, 10, 4, 110, 3] вернет [4, 3].
Этот код расширяет протокол (Collection) только там, где он подходит под условие ограничения: элементы в коллекции должны соответствовать другому протоколу, Comparable. Это само по себе, конечно, крутая штука, но давайте остановимся на минуточку: что если мы хотим чего-то более конкретного? Swift 3.0 позволяет нам расширить конкретный тип, а не протокол Collection, поэтому мы можем написать так:
extension Array where Element: Comparable {
func lessThanFirst() -> [Element] {
guard let first = self.first else { return [] }
return self.filter { $0 < first }
}
}
let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)
Это расширит конкретный тип (только Array), но по-прежнему продолжит использование протокола для условия ограничения. Что делать, если мы хотим чего-то еще более конкретного - расширить конкретный тип с конкретным условием, например, только массивы содержащие целые числа ([Int])? Получается, что в Swift 3.0 этого нельзя сделать, и это обычно озадачивает: ведь если Swift 3.0 может обрабатывать расширения протоколов с другим протоколом в качестве условия ограничения, то уж расширить определенный тип с определенным констрейнтом должно быть плевым делом?
К счастью, это несоответствие было ликвидировано в Swift 3.1, и это означает, что теперь мы сможем написать код следующим образом:
extension Array where Element == Int {
func lessThanFirst() -> [Int] {
guard let first = self.first else { return [] }
return self.filter { $0 < first }
}
}
let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)
Это расширит конкретный тип (только Array) и будет использовать конкретное условие (только там, где элементы имеют тип Int ).
Понятно, что наш пример здесь не сильно значим, но в вашем собственном коде это будет значительно более полезным, если вы хотите расширить массивы, содержащие свои кастомные структуры.
Дженерики с вложенными типами
Поддержка вложенных типов в Swift 3.0 полезна для организации ваших данных и увеличения инкапсуляции, но в Swift 3.1 они переходят на следующий уровень - добавляется поддержка дженериков. Давайте для начала рассмотрим простой пример:
struct Message {
struct Attachment {
var contents: String
}
var title: String
var attachment: Attachment
}
Код выше создает структуру Message, которая имеет внутреннюю структуру Attachment - вложенный тип. Я добавил два свойства String, так как сообщения будут содержать некоторый текст и у вложений также будет текст.
Теперь, что если мы хотим чтобы у Message или у Attachment были различные типы данных - возможно Int или Data? Что ж, для этого нужны дженерики, так что вы возможно напишите что-то подобное:
struct Message {
struct Attachment {
var contents: String
}
var title: T
var attachment: Attachment
}
Это сообщит Swift, что мы хотим чтобы Message работало по нескольким типам данных, и независимо от типа данных, использованных для создания структуры, использовать их для свойства title. Или, по крайней мере, это сказало бы Swift, если такой код бы на самом деле был легальным. Но Swift 3.0 не позволяет смешивать вложенные типы с дженериками. К счастью, это именно то, что Swift 3.1 позволит, потому что вложенные типы теперь могут появляться внутри дженериков.
Не желая останавливаться на достигнутом, Swift 3.1 идет еще дальше: вложенные типы также могут быть дженериками, либо используя свой собственный дженерик, либо наследуя тип дженерика своего родителя. Например:
struct Message {
struct Attachment {
var contents: T
}
var title: T
var attachment: Attachment
}
С помощью этого кода, у структуры Message будет определенный тип, на который она будет подписана, а структура Attachment всегда будет иметь один и тот же тип - вы не можете использовать String для одного и Int для другого. Таким образом, следующий код будет нормально работать:
let msg = Message(title: "Hello", attachment: Message.Attachment(contents: "World"))
Хорошо, если ваша цель состоит в том, чтобы вложенный тип и его контейнер использовали один и тот же дженерик, вам даже не нужно будет объявлять вложенный тип в качестве дженерика - Swift сделает внешний тип доступным для вложенного типа, так что вы можете просто написать вот так:
struct Message {
struct Attachment {
var contents: T
}
var title: T
var attachment: Attachment
}
Дженерики великолепны и также великолепны вложенные типы, так что я очень рад, что в Swift 3.1 их наконец объединили.
У Sequence появляются методы prefix(while:) и drop(while:)
Два новых полезных метода были добавлены к протоколу Sequence: prefix(while:)и drop(while:). Первый возвращает самую длинную подпоследовательность, которая удовлетворяет предикат, что является причудливым способом сообщить то, что вы даете клоужеру срабатывать на каждом элементе, и он будет проходить через все элементы в последовательности и возвращать те, которые соответствуют замыканию - но он остановится, как только найдет несоответствующий элемент.
Давайте рассмотрим пример:
let names = ["Michael Jackson", "Michael Jordan", "Michael Caine", "Taylor Swift", "Adele Adkins", "Michael Douglas"]
let prefixed = names.prefix { $0.hasPrefix("Michael") }
print(prefixed)
Здесь используется метод hasPrefix() для возврата подпоследовательности ["Michael Jackson", "Michael Jordan", "Michael Caine" - первых три элемента в последовательности. "Michael Douglas" включен не будет, потому что он идет после первого не-Майкла. Если вы хотите всех Майклов независимо от их позиции, то вам следует использовать filter().
Второй новый метод drop(while:) эффективен в обратном: он находит самую длинную подпоследовательность, которая удовлетворяет вашему предикату, и возвращает все, что находится после него. Например:
let names = ["Michael Jackson", "Michael Jordan", "Michael Caine", "Taylor Swift", "Adele Adkins", "Michael Douglas"]
let dropped = names.drop { $0.hasPrefix("Michael") }
print(dropped)
Это вернет подпоследовательность ["Taylor Swift", "Adele Adkins", "Michael Douglas"] - все, что идет после первоначальных Майклов.
С нетерпением ожидая Swift 4.0 …
Swift 4.0 почти наверняка увидит свет в июне, а полный релиз появится спустя несколько месяцев. И хотя главной его целью является обеспечение стабильности кода на Swift 3, все же некоторые критические изменения почти неизбежны.
Если вы хотите заглянуть в будущее, вы можете прочитать String Processing For Swift document от двух Swift инженеров компании Apple - да, код будет нарушен (сломан), но, я надеюсь, что в конечном счете все это приведет к тому, что разработчикам будет проще работать с кодом. Если вы читали о проблемах с кодом, описанные в моей книге Swift Coding Challenges, то вы имеете представление о том, какими своеобразными сейчас могут быть строки.
То, на что мы все рассчитываем - это реальная совместимость ABI, что позволит разработчикам распространять скомпилированные библиотеки – одна из немногих ключевых недостающих функций, которая осталась в Swift сегодня. Это также уменьшит размер каждого Swift приложения, потому что iOS может хранить Swift's runtime libraries, а не передавать их с каждым Swift приложением, таким образом мы получаем более быстрые загрузки в ITunes Connect, более быструю загрузку для пользователей и красивые стройные приложения. Ура!
This article was translated with permission from the English original, What's new in Swift 3.1? on Hacking with Swift.