Swift 5.0. Что нового?

Swift 5 — долгожданный релиз, включающий в себя несколько десятков улучшений и исправлений. Но самой главной целью релиза Swift 5.0 было достижение ABI стабильности. В этой статье вы узнаете, что такое ABI и что стабильный ABI даст iOS/macOS разработчикам. А также проведём разбор нескольких новых фич Swift 5.

-p8szkb7m3mn49xjavc3_nvkjyk.png

ABI — бинарный интерфейс приложения. ABI можно рассматривать как набор правил, позволяющих компоновщику объединять откомпилированные модули компонента.

Соответственно, в ABI описано следующее.
1). То, как происходит вызов кода из разных модулей, в том числе системных.
2). Формат передачи аргументов и получение возвращаемого значения из функций.
3). Алгоритмы лэйаута данных в оперативной памяти.
4). Управление памятью, ARC.
5). Система типов, дженерики.

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 стабильности.

1). Приложения станут весить меньше.
2). Ускорение запуска и производительности приложений.
3). В теории, Apple может писать новые фреймворки полностью на Swift.

Минусы ABI стабильности.

Разработчикам придётся учитывать отсутствие в более старых версиях стандартной библиотеки какого-либо нового функционала. Например, если в iOS 13 будет встроен Swift 5.1 с какими-нибудь новыми классами/функциями в стандартной библиотеке, то при поддержке в приложении iOS 12.2 разработчики не смогут их использовать. (Нужно будет вставлять проверки #available (…) так же, как мы это делаем сейчас для Foundation, UIKit и других платформенных библиотек).

В стандартной библиотеке появился удобный способ передачи и обработки ошибок в асинхронном API. Также этот тип можно использовать в случае, если по каким-либо причинам нам не подходит стандартная обработка ошибок через try/catch.

Тип Result реализован через enum с двумя кейсами: success и failure:

public enum Result 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) -> 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)")
    }
}

В 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 позволяет преобразовать значения словаря, а также отфильтровать их, если само преобразование возвратило 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«ом.

В 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, то должны реализовать один (или оба) из методов:

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.

© Habrahabr.ru