Swift 4.0 — это новая версия многими любимого языка программирования с новыми функциями, которые позволяют нам писать более простой и безопасный код. Вы с удовольствием узнаете, что это не так драматично как эпичные изменения в Swift 3.0 и большинство изменений имеют обратную совместимость с вашим сущетсвующим кодом на Swift. Конечно вам потребуется некоторое время для внесения изменений, но это не должно занять много времени.
ПРЕДУПРЕЖДЕНИЕ: Swift 4 на момент написания и подготовки перевода статьи находится в активной разработке и автором оригинала были выбраны только некоторые из наиболее интересных и полезных новых функций для обсуждения. Пожалуйста, имейте в виду, что больше возможностей будет доступно ближе к релизу.
Swift'овое кодирование и декодирование
Мы знаем, что типы значений велики, но еще мы знаем, что они ужасно взаимодействуют с Objective-C API, такими как NSCoding — вам нужно писать некую прослойку или использовать классы, при этом оба варианта неприятны. Хуже того, даже если вы переключаетесь на классы вам нужно писать методы кодирования и декодирования вручную, написание которых является болезненным и подверженным ошибкам.
Swift 4 вводит новый протокол Codable, который позволяет вам сериализовать и десериализовать собственные типы данных без написания дополнительного кода и не беспокоиться о потере ваших типов данных. Более того, вы можете выбрать как вы хотите сериализовать данные: использовать классический формат property list или JSON.
Да, вы все прочитали правильно: Swift 4 позволяет вам сериализовать ваши собственные типы данных в JSON без написания специального кода.
Давайте посмотрим насколько это прекрасно. Во-первых вот пользовательский тип данных и некоторые его экземпляры:
struct Language: Codable {
var name: String
var version: Int
}
let swift = Language(name: "Swift", version: 4)
let php = Language(name: "PHP", version: 7)
let perl = Language(name: "Perl", version: 6)
Как вы можете видеть, к структуре Language я подключил протокол Codable. С этим крошечным дополнением мы можем конвертировать это в JSON, представленный как Data следующим способом:
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
// сохраняем `encoded` где-нибудь
}
Swift автоматически кодирует все свойства внутри вашего типа данных, вам не нужно делать дополнительных действий.
Теперь вы, если также как и я долгое время использовали NSCoding, вероятно несколько сомневаетесь: действительно ли это то, что нам нужно и как мы можем быть уверены, что это работает? Давайте добавим немного кода чтобы попробовать преобразовать объект Data так чтобы мы могли отобразить это в консоли, тогда нам нужно декодировать это назад в новый экземпляр структуры Language:
if let encoded = try? encoder.encode(swift) {
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Language.self, from: encoded) {
print(decoded.name)
}
}
И JSONEncoder и PropertyListEncoder имеют множество вариантов настройки. Для получения дополнительной информации о других вариантах смотрите предложения по развитию этой новой функциональности.
Многострочные строковые литералы
Написание многострочных строк в Swift всегда означало добавление \n внутри ваших строк для добавления переноса строки, там где вы этого хотите. В коде это выглядит не очень хорошо, но по крайней мере корректно отображает информацию для пользователей. К счастью, Swift 4 добавляет новый синтаксис для многострочных строковых литералов, который позваляет добавлять переносы строк и использовать кавычки без экранирования, в то же время доступна такая полезная функциональность, как строковая интерполяция.
Чтобы начать строковый литерал, вам нужно: написать три двойных кавычки """ и нажать [Enter]. Тогда вы можете продолжить писать строку сколько хотите включая переменные и разрывы строк, до завершения вашей строки нажатием [Enter] и написанием трех двойных кавычек """.
Я хотел бы уточнить о нажатии переноса строки потому что строковый литералы имеют два важных правила:
1) когда вы открываете строку используя """ содержимое вашей строки должно начинаться на новой строке;
2) когда вы закрываете строку используя """ это обозначение также должно быть в начале новой строки.
Здесь это в действии:
let longString = """
When you write a string that spans multiple
lines make sure you start its content on a
line all of its own, and end it with three
quotes also on a line of their own.
Multi-line strings also let you write "quote marks"
freely inside your strings, which is great!
"""
Это создает новую строку с несколькими разрывами строк прямо в определении — гораздо проще читать и писать.
Для дополнительной информации смотрите предложения по развитию этой новой функциональности.
Улучшенные пути (keypaths) в KVC (Key-Value Coding)
Одной из самых любимых особенностей Objective-C является способность ссылаться на свойство динамически, а не напрямую — то есть, иметь возможность сказать «вот объект X, здесь его свойство, которое я хотел бы прочитать», не читая его вообще. Эти ссылки, названные keypaths, отличаются от прямых доступов к свойствам, потому что они фактически не читают или не записывают значение, они просто скрывают их чтобы использовать позже.
Если вы ранее не использовали keypaths позвольте мне показать вам аналогию того как они работают с использованием обычных методов Swift. Мы собираемся определить структуры Starship и Crew, затем создать по одному экземпляру каждой:
// структура для примера
struct Crew {
var name: String
var rank: String
}
// другая структура, в этот раз с методом
struct Starship {
var name: String
var maxWarp: Double
var captain: Crew
func goToMaximumWarp() {
print("\(name) is now travelling at warp \(maxWarp)")
}
}
// создаем экземпляры наших структур
let janeway = Crew(name: "Kathryn Janeway", rank: "Captain")
let voyager = Starship(name: "Voyager", maxWarp: 9.975, captain: janeway)
// захватываем ссылку на `goToMaximumWarp()` метод
let enterWarp = voyager.goToMaximumWarp
// вызываем ссылку
enterWarp()
Поскольку функции в Swift являются типами первого класса последние две строки могут создать ссылку на метод goToMaximumWarp() и вызвать позже когда нам это понадобится. Проблема в том, что мы не можем сделать то же самое для свойств — мы неможем сказать «создай ссылку на свойство имени капитана, которое я смогу проверить когда произойдет неизбежный бунт», потому что Swift просто прочитает свойство напрямую и вы просто получите исходное значение.
Это исправлено с помощью keypaths: они являются ссылками на свойства как наш код enterWarp(). Если вы вызываете ссылку сейчас вы получаете текущее значение, но если вы вызываете ссылку позже, вы получите самое последнее значение. Вы можете добраться через любое количество свойств и Swift использует свой тип вывода, чтобы гарантировать, что вы вернете правильный тип.
Сообщество развивающее Swift потратило много времени на обсуждение правильного синтаксиса для keypaths потому что это должно быть что-то визуально отличное от другого Swift кода и в конце концов этот синтаксис использует обратные косые черты: \Starship.name, \Starship.maxWarp, и \Starship.captain.name. Вы можете присвоить эти значения переменным и использовать тогда когда вы этого хотите любому экземпляру структуры Starship. Например:
let nameKeyPath = \Starship.name
let maxWarpKeyPath = \Starship.maxWarp
let captainName = \Starship.captain.name
let starshipName = voyager[keyPath: nameKeyPath]
let starshipMaxWarp = voyager[keyPath: maxWarpKeyPath]
let starshipCaptain = voyager[keyPath: captainName]
Это сделает starshipName как String, а starshipMaxWarp — Double потому что Swift способен правильно выводить типы данных. Третий пример берет свойство свойства и Swift также корректно определяет его.
Для дополнительной информации смотрите предложения по развитию этой новой функциональности.
Улучшенная функциональность словарей
Одним из самых интригующих предложений для Swift 4 было добавление некоторой новой функциональности в словари, чтобы сделать их более мощными, а также сделать их поведение более предсказуемым в определенных ситуациях.
Давайте начнем с простого примера: фильтрация словарей в Swift 3 не возвращает новый словарь. Вместо этого в результате фильтрации мы получаем массив кортежей (tuples) с метками «ключ-значение». Например:
let cities = ["Shanghai": 24_256_800, "Karachi": 23_500_000, "Beijing": 21_516_000, "Seoul": 9_995_000];
let massiveCities = cities.filter { $0.value > 10_000_000 }
После того как это код выполнится вы не можете использовать для получения нужных данных такую запись:
massiveCities["Shanghai"]
потому что это уже не словарь. Вместо этого вам нужно использовать такой код:
massiveCities[0].value
и это не очень-то здорово.
Начиная с Swift 4 этот код ведет себя так как вы ожидали бы — фильтрация вернет новый словарь.
Да, очевидно, что это сломает любой существующий код, в котором используется массив кортежей как возвращаемое значение.
Точно также метод map() со словарями не работал так, как надеялись многие люди: вы получали переданный кортеж вида «ключ-значение» кроме того это могло быть одно значение добавленное в массив. Например:
let populations = cities.map { $0.value * 2 }
На данный момент это не изменено в Swift 4, но появился новый метод mapValues(), который должен быть значительно полезнее, потому что этот метод позволяет преобразовывать значения и помещать их обратно в словарь с использованием исходных ключей.
Например, этот код будет преобразовывать числовое значение и преобразовывать в строку данные о населении городов и складывать назад в новый словарь с такими же ключами: Shanghai, Karachi, и Seoul:
<let roundedCities = cities.mapValues { "\($0 / 1_000_000) million people" }
(В случае если вам интересно, делать маппинг ключей словаря небезопасно, т.к. можно случайно создать дубликаты.)
Мое любимое дополнение для словаря это инициализатор группировки, который преобразует последовательность в словарь последовательностей, которые сгруппированы по вашему желанию. Продолжая наш пример с городами мы могли бы использовать cities.keys, чтобы вернуть массив имен городов, а затем сгруппировать их по первой букве:
let groupedCities = Dictionary(grouping: cities.keys) { $0.characters.first! }
print(groupedCities)
Это выведет в консоль следующее:
["B": ["Beijing"], "S": ["Shanghai", "Seoul"], "K": ["Karachi"]]
Или мы могли бы сделать группировку городов на основе длин их имен:
let groupedCities = Dictionary(grouping: cities.keys) { $0.count }
print(groupedCities)
Это выведет в консоль следующее:
[5: ["Seoul"], 7: ["Karachi", "Beijing"], 8: ["Shanghai"]]
Наконец, сейчас возможно получение доступа к ключу словаря и предоставить значение по умолчанию, если указанный ключ отсутствует:
let person = ["name": "Taylor", "city": "Nashville"]
let name = person["name", default: "Anonymous"]
Сейчас любой опытный разработчик возможно будет утверждать что аналогичного результата можно добиться проще и я с этим согласен. Мы могли бы написать это таким образом в текущей версии Swift:
let name = person["name"] ?? "Anonymous"
Однако это не работает, если вы изменяете значение словаря, а не просто получаете его. Вы не можете сразу изменить значение словаря, потому что доступ по этому ключу возвращает опциональный тип — ключ может не существовать. С помощью значений словаря по умолчанию в Swift 4 вы можете написать более сжатый код, например:
var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"]
var favoriteCounts = [String: Int]()
for show in favoriteTVShows {
favoriteCounts[show, default: 0] += 1
}
Этот цикл перебирает каждую строку в favoriteTVShows и использует словарь favoriteCounts чтобы отслеживать как часто появляется каждый элемент. Мы можем менять словарь в одну строку кода, потому что мы знаем что он всегда будет иметь значение: или 0 как значение по умолчанию или некоторое большее число основанное на предыдущем подсчете.
Для дополнительной информации смотрите предложения по развитию этой новой функциональности.
Строки снова коллекции!
Это маленькое изменение, но гарантировано сделает многих людей счастливыми: строки снова являются коллекциями. Это означает, что теперь вы можете их обратить, перебрать по символам, использовать map() и flatMap() и т.д. Например:
let quote = "It is a truth universally acknowledged that new Swift versions bring new features."
let reversed = quote.reversed()
for letter in quote {
print(letter)
}
Это изменение было внесено как часть набора поправок, называнный «Манифест строк».
Односторонние диапазоны
Последнее, но не менее важное — в Swift 4 появляется Python-подобная односторонняя нарезка коллекций, где недостающая сторона диапазона автоматически определяется как начало или конец коллекции. Это не влияет на существующий код, потому что это новый подход к существующему оператору, таким образом вы можете не беспокоиться о потенциальной поломке в коде. Примеры:
let characters = ["Dr Horrible", "Captain Hammer", "Penny", "Bad Horse", "Moist"]
let bigParts = characters[..<3]
let smallParts = characters[3...]
print(bigParts)
print(smallParts)
Этот код выводит в консоль сначала это:
["Dr Horrible", "Captain Hammer", "Penny"]
а затем это:
["Bad Horse", "Moist"]
Для дополнительной информации смотрите предложения по развитию этой новой функциональности.
Еще больше впереди...
На момент перевода статьи разработчикам уже доступен Xcode 9 Beta 3 (с 10.07.2017) вместе с iOS 11, tvOS 11, watchOS 4, и новой версией macOS. То что мы видели уже многообещающе, потому что понятно, что команда усердно трудится чтобы сделать Swift 4 как можно лучше. Речь идет прежде всего о добавлении новых функций, а не об изменении существующих и это должно сделать легче переход на новую стабильную версию языка.
Не смотря на то, что развитие Swift иногда может быть хаотичным, Swift 4 снова подтверждает подход сообщества Apple. Я добавил несколько предложений, каждое из предложений было широко обсуждено сообществом чтобы достичь согласия — это не просто инженеры Apple, которые форсируют изменения просто потому что они могут, вместо этого они отличаются разумным и продуманным подходом, чтобы улучшить то что уже является умным и элегантным языком.
Одной из функций, которая была отложена, является совместимость с ABI, которая позволила бы разработчикам распространять скомпилированные библиотеки — одну из немногих ключевых недостающих функций, которые остаются в Swift сегодня. Надеюсь, мы доберемся до этого в Swift 5…
Комментарии (9)
iosdevel
20.07.2017 10:31-2Вышел Swift 0.4.
1. Тормознуты и глючные инструменты разработки (ну я не про конвертер, конечно).
2. Большой размер приложения.
3. Утечки памяти, без шанса пофиксить.
4. Падение fps в SceneKit.
5. Отсутствие автоматического приведения типов.
6. Опциональные типы — для пыток их что ли придумали.
7. Cообщения об ошибках при отладке на девайсе, да кому они нужны.
9. dictionaryOk = (dictionary as! NSDictionary as? Dictionary<String, AnyObject>)! as NSDictionary, это от души…
10. Когда гуглишь, сначала дура какая-то все время на первой странице. Потом StackOverflow уже.
11. Amazing
12. Awersome
AKhatmullin
20.07.2017 10:33Haters gonna hate.
Если это все причиняет столько боли, то почему бы не выбрать другие направления и/или средства разработки?
tapoton
20.07.2017 16:49+2Опциональные типы — для пыток их что ли придумали
Удобно и безопасно, если вы не умеете с ними работать, это не значит, что они не нужны.
dictionaryOk = (dictionary as! NSDictionary as? Dictionary<String, AnyObject>)! as NSDictionary
Вы так в production коде делаете? Удачи вам и хорошего настроения.iosdevel
20.07.2017 18:09-2NSString *string = nil;
NSInteger lenght = [string lenght];
Вот это, студент, удобно и безопасно )
А код да, из прода, но не мой. Может твой?tapoton
20.07.2017 22:49+21. Мы на «ты» не переходили, профессор.
2.lenghtпишется length. А о безопасности расскажи кому-нибудь ещё. Получил nil, получил из него 0 и думай, почему у меня там обработка такая, будто объект есть, хотя его нет в природе.
3. По себе людей не судят. Раз ты считаешь, что такой код имеет место в проде, значит и сам так можешь написать. Я такой код сам не пишу и на код ревью не пропущу.
uninova
20.07.2017 16:48Добавлю, что появилась фича, которая была в objc, а именно комбинировать типы и протоколы ( логическое И)
protocol HeaderView {} class ViewController: NSViewController { let header: NSView & HeaderView }
daspisch
Лучше бы дженерики нормальными сделали, вместо того чтобы переливать из пустого в порожнее добавляя реализацию уже существующих библиотек, или отменяя свои предыдущие недальновидные решения(ждём когда вернут операторы инкремента/декремента)?
AKhatmullin
У нас в офисе однажды тоже разгорелась дискуссия об операторах инкремента/декремента. К соглашению не пришли. Одна часть считает их необходимыми разработчику как воздух и кофе, другие (в их числе и я) уже и забыли когда они крайний раз нужны были.