company_banner

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

    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.

    • +39
    • 11,5k
    • 7
    Авито
    311,00
    У нас живут ваши объявления
    Поделиться публикацией

    Комментарии 7

      +1
      Оперативненько, спасибо!
        0
        Спасибо, мы старались! Какая из фичей вам больше всего понравилась?
          0
          Разумеется ABI, а далее весьма интересна поддержка Result.
            –1
            Фигней занимаются. Все ждем актеров или корутин, а тут вот тебе, ABI и Strings…
              0
              Да, акторов и корутин очень хочется.
              Но все таки для Apple очень важен стабильный ABI.
              Так что ждём Swift 6 ))
          0
          Спасибо!
          незначительная помарка в первом if — «4.2 и меньше»
            0
            Спасибо! Исправили опечатку!

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое