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


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



ABI Stability


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 и других платформенных библиотек).


Тип 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(>=5)
// Этот код будет скомпилирован только для свифт 4.2 и меньше
#endif

// Swift 5
#if swift(<5)
// Этот код будет скомпилирован только для свифт 4.2 и меньше
#endif

Заключение


Это не все возможности и улучшения появившиеся в Swift 5. Всего было принято 28 предложений от комьюнити, также включающие в себя повышение производительности строк, улучшения Swift Package Manager и стандартной библиотеки. Полный список изменений и улучшений можно посмотреть в release notes.




К сожалению, не доступен сервер mySQL