Swift 5 — долгожданный релиз, включающий в себя несколько десятков улучшений и исправлений. Но самой главной целью релиза Swift 5.0 было достижение ABI стабильности. В этой статье вы узнаете, что такое ABI и что стабильный ABI даст iOS/macOS разработчикам. А также проведём разбор нескольких новых фич Swift 5.
ABI Stability
ABI — бинарный интерфейс приложения. ABI можно рассматривать как набор правил, позволяющих компоновщику объединять откомпилированные модули компонента.
Соответственно, в ABI описано следующее.
- То, как происходит вызов кода из разных модулей, в том числе системных.
- Формат передачи аргументов и получение возвращаемого значения из функций.
- Алгоритмы лэйаута данных в оперативной памяти.
- Управление памятью, ARC.
- Система типов, дженерики.
Swift 5 вместе со стабильным ABI предоставляет бинарную совместимость для приложений. Бинарная совместимость для iOS/macOS приложений означает, что скомпилированные приложения будут в рантайме совместимы с системными библиотеками, скомпилированными более ранними или более поздними версиями языка. Например, приложение, скомпилированное с Swift 5.0, будет совместимо с стандартными библиотеками, скомпилированными с Swift 5.1 или Swift 6.0.
Начиная с iOS 12.2 и macOS 10.14.4, операционные системы Apple будут содержать все необходимое для запуска свифтовых приложений. Это означает, что приложения, написанные на Swift 5 и более поздних версиях, не будут содержать рантайм и стандартную библиотеку языка. Поэтому приложения, написанные на Swift 5, станут весить примерно на 3-10 мегабайт меньше.
Важно отметить, что помимо ABI stability, есть еще Module stability. Если ABI stability позволяет совмещать разные версии свифта в рантайме, то Module stability отвечает за то, как компилируются бинарные фреймворки, написанные на разных версиях языка. Module stability появится в Swift 5.1. И тогда разработчики смогут распространять свои фреймворки не только с открытым исходным кодом, но и в скомпилированном виде.
Плюсы ABI стабильности.
- Приложения станут весить меньше.
- Ускорение запуска и производительности приложений.
- В теории, Apple может писать новые фреймворки полностью на Swift.
Минусы ABI стабильности.
Разработчикам придётся учитывать отсутствие в более старых версиях стандартной библиотеки какого-либо нового функционала. Например, если в iOS 13 будет встроен Swift 5.1 с какими-нибудь новыми классами/функциями в стандартной библиотеке, то при поддержке в приложении iOS 12.2 разработчики не смогут их использовать. (Нужно будет вставлять проверки #available(...) так же, как мы это делаем сейчас для Foundation, UIKit и других платформенных библиотек).
Тип Result в стандартной библиотеке
В стандартной библиотеке появился удобный способ передачи и обработки ошибок в асинхронном API. Также этот тип можно использовать в случае, если по каким-либо причинам нам не подходит стандартная обработка ошибок через try/catch.
Тип Result реализован через enum с двумя кейсами: success и failure:
public enum Result<Success, Failure> where Failure: Error {
case success(Success)
case failure(Failure)
...
}
На самом деле такой подход не нов для Swift-разработчиков. Ещё со времен первой версии Swift многие разработчики использовали похожий подход. Но теперь, когда в стандартной библиотеке появился свой Result, это упростит взаимодействие с кодом из внешних библиотек.
Пример использования в сервисе загрузок статей:
struct Article {
let title: String
}
class ArticleService {
func fetchArticle(id: Int64, completion: @escaping (Result<Article, Error>) -> Void) {
// асинхронная загрузка статьи
// ...
completion(.success(Article(title: "Swift 5.0. Что нового?")))
}
}
А вот пример обработки полученного результата. Так как Result — это всего лишь enum, то мы можем обработать все его состояния с помощью switch:
articleService.fetchArticle(id: 42) { result in
switch result {
case .success(let article):
print("Success: (article)")
case .failure(let error):
print("Failure: (error)")
}
}
Raw strings
В Swift 5 добавили так называемые raw strings, в которых кавычки и бэкслеш интерпретируются именно как символы, и для их использования в литерале не нужно использовать символ экранирования. Чтобы написать литерал такой строки, необходимо к двойным кавычкам по краям добавить символ #.
Пример использования кавычек:
// swift 4.2
print("Чтобы вывести строку в "кавычках" необходимо добавлять бэкслеш.")
print("Чтобы добавить переход на следующую строку, нужно использовать символы \n")
// swift 5
print(#"В "сырой" строке не нужны бэкслеши перед кавычками"#)
print(#"Чтобы добавить переход на следующую строку, нужно использовать символы n"#)
Эта фича особенно полезна при написании регулярных выражений:
// swift 4.2
let regex = "^\(*\d{3}\)*( |-)*\d{3}( |-)*\d{4}$"
// swift 5
let regex = #"^(*d{3})*( |-)*d{3}( |-)*d{4}$"#
Для интерполяции строк после бэкслеша надо добавлять символ #:
// swift 4.2
let string = "Строка с интерполяцией (variable)"
// swift 5
let string = #"Строка с интерполяцией #(variable)"#
Более подробно можете прочитать в этом предложении.
Обновленная интерполяция строк
С помощью интерполяции строк мы можем добавить в строковый литерал значение какой-либо переменной или результат выражения. Начиная с 5-ой версии языка, появилась возможность расширять то, как наши выражения добавляются в конечную строку.
В общем случае достаточно написать расширение к структуре DefaultStringInterpolation и добавить метод с названием appendInterpolation. Например, если мы хотим добавить в строку цену в отформатированном виде:
extension DefaultStringInterpolation {
mutating func appendInterpolation(price: Decimal) {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
if let string = formatter.string(from: price as NSDecimalNumber) {
appendLiteral(string)
} else {
appendLiteral(price.description)
}
}
}
print("Price of item: (price: 9.99)")
// Price of item: $9.99
Важно отметить то, что, по сути, конструкция (price: 9.99) в строке с помощью компилятора преобразовалась в вызов метода appendInterpolation(price: Decimal).
Также в методах appendInterpolation мы можем добавить неограниченное число аргументов, как именованных, так и не именованных, с дефолтными значениями или без них.
Более подробно можно прочитать в этом предложении.
Проверка кратности чисел
К числовым типам в стандартной библиотеке добавлен метод проверки кратности isMultiple(of:). Да, мы всё ещё можем использовать оператор взятия остатка от деления %. Но, кажется, isMultiple(of:) выглядит более наглядно.
let interger = 42
if interger.isMultiple(of: 3) {
print("Кратно трем")
} else {
print("Не кратно трем")
}
Метод compactMapValues в Dictionary
Метод compactMapValues позволяет преобразовать значения словаря, а также отфильтровать их, если само преобразование возвратило nil.
Например, маппинг строковых ключей в тип URL:
let dict = [
"site": "https://www.site.ru/path/to/web/site/page",
"other site": "invalid url"
]
let mappedDict: [String: URL] = dict.compactMapValues { URL(string: $0) }
print(mappedDict)
// ["site": https://www.site.ru/path/to/web/site/page]
Вторая пара «ключ/значение» была удалена после маппинга, так как строка не является валидным URL’ом.
Изменение поведения try?
В Swift 4.2 с помощью конструкции try? можно с лёгкостью получить опциональный тип с несколькими уровнями вложенности. В большинстве случаев это не то, чего ожидает разработчик. По этой причине в Swift 5 try? получил поведение, схожее c optional chaining. То есть при комбинации try? с optional chaining или optional casting результатом выражения будет опционал с одним уровнем вложенности.
Пример использования try? вместе с as?:
// Swift 4.2
let jsonDict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
// Тип jsonDict - [String: Any]??
// Swift 5
let jsonDict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
// Тип jsonDict - [String: Any]?
Пример использования try? вместе с методом опционального объекта:
// Swift 4.2
let article = try? storage?.getArticle()
// Тип article - Article??
// Распаковка
if let first = article, let second = first {
first // тип Article?
second // тип Article
}
// Или так
if case let value?? = article {
value // тип Article
}
// Swift 5
let article = try? storage?.getArticle()
// Тип article - Article?
// Распаковка
if let value = article {
value // тип Article
}
Более подробно можно прочитать в этом предложении.
Атрибут @dynamicCallable
Новый атрибут @dynamicCallable позволяет пометить тип как «вызываемый». Это означает, что мы сможем вызвать тип как обычный метод.
Если мы помечаем тип как @dynamicCallable, то должны реализовать один (или оба) из методов:
func dynamicallyCall(withArguments: <#Arguments#>) -> <#R1#>
func dynamicallyCall(withKeywordArguments: <#KeywordArguments#>) -> <#R2#>
Тип Arguments должен поддерживать протокол ExpressibleByArrayLiteral, тип KeywordArguments должен поддерживать протокол ExpressibleByDictionaryLiteral, а R1 и R2 могут быть любыми типами.
Например, структура Sum. При её вызове можно передать любое количество чисел и получить их сумму:
@dynamicCallable
struct Sum {
func dynamicallyCall(withArguments args: [Int]) -> Int {
return args.reduce(0, +)
}
}
let sum = Sum()
let result = sum(1, 2, 3, 4)
print(result) // 10
По сути, компилятор преобразует sum(1, 2, 3, 4) в вызов sum.dynamicallyCall(withArguments: [1, 2, 3, 4]). Аналогично для метода dynamicallyCall(withKeywordArguments:).
Эта фича позволит добавить взаимодействие Swift кода с различными динамическим языками программирования, например, Python или JavaScript.
Более подробно можно прочитать в этом предложении.
Поддержка оператора «меньше» в директивах проверки версии компилятора и языка
Начиная с 5-ой версии Свифта можно использовать оператор «меньше» при проверках версии компилятора в коде:
// Swift 4.2
#if !swift(>=4.2)
// Этот код будет скомпилирован только для свифт 4.2 и меньше
#endif
// Swift 5
#if swift(<5)
// Этот код будет скомпилирован только для свифт 4.2 и меньше
#endif
Заключение
Это не все возможности и улучшения появившиеся в Swift 5. Всего было принято 28 предложений от комьюнити, также включающие в себя повышение производительности строк, улучшения Swift Package Manager и стандартной библиотеки. Полный список изменений и улучшений можно посмотреть в release notes.
Автор: aagarunov