company_banner

Что нового в Swift 5?

    Привет, меня зовут Илья. Я — iOS разработчик в компании Tinkoff.ru. В этой статье я сделаю краткий обзор основных изменений в Swift 5. Данные изменения описаны в release notes. Для тех, кто еще не ознакомился, добро пожаловать под кат!



    Размер приложения уменьшится!


    Приложения, написанные на Swift 5 и собранные для iOS 12.2, watchOS 5.2, tvOS 12.2, не будут включать динамические библиотеки для Swift standard library и Swift SDK. А это значит, что размер приложения уменьшится, правда, не намного. Если верить этому твиту, размер пустого проекта сократился с 2.4 Мб до 24 Кб. Неплохой результат для маленьких приложений, но для больших особой разницы не будет.

    @dynamicCallable (SE-0216)


    Атрибут @dynamicCallable позволяет работать с объектом как с функцией. Такие объекты называются функциональными объектами или функторами (подробнее можно почитать тут). Функциональные объекты есть в C++, Python, JavaScript и в других языках, а в Swift их добавили для совместимости с этими языками. Дело в том, что сейчас Swift хорошо взаимодействует с API C и Objective-C и разработчики языка хотят добавить взаимодействие с динамическими языками — Python, JavaScript, Ruby и другими.

    Для того, чтобы сделать тип функтором, необходимо добавить к его объявлению атрибут @dynamicCallable. Рассмотрим пример структуры Reducer, с помощью которой можно будет сложить числа в массиве:

    @dynamicCallable struct Reducer { ... }
    

    После чего нужно реализовать один или оба из следующих методов:

    func dynamicallyCall(withArguments: ExpressibleByArrayLiteral)
    func dynamicallyCall(withKeywordArguments: ExpressibleByDictionaryLiteral)
    

    Первая функция позволяет обращаться к объекту, передавая в качестве аргументов массив. Вторая функция позволяет обращаться к объекту, передавая в качестве аргументов тот же массив, но используя названия аргументов.

    Например, реализация первой функции для структуры Reducer будет выглядеть так:

    func dynamicallyCall(withArguments arguments: [Int]) -> Int {
        return arguments.reduce(0, +)
    }
    

    После чего применять такую структуру можно следующим образом:

    let reducer = Reducer()
    let sum = reducer(1, 2, 3) // sum = 6
    

    Реализацию второго метода рассмотрим на примере структуры Comparator, с помощью которой можно сравнить два числа:

    @dynamicCallable struct Comparator {
        func dynamicallyCall(withKeywordArguments arguments: KeValuePairs<String, Int>) -> ComparisonResult {
            guard let lhs = arguments["lhs"], let rhs = arguments["rhs"], lhs != rhs else { return .orderedSame }
            return lhs > rhs ? .orderedDescending : .orderedAscending
        }
    }
    

    Воспользоваться этой структурой можно следующим образом:

    let comparator = Comparator()
    let comparisionResult = comparator(lhs: 1, rhs: 2) // comparisionResult = .orderedAscending
    

    Атрибут unknown в switch (SE-0192)


    Многие знают, что при обработке значений перечисления нужно описать все случаи и стараться не использовать default. Это требование хоть и добавляет щепотку безопасности, но также имеет недостаток, так как при изменении значений в перечислении нужно добавить их обработку. Еще есть вероятность, что системный фреймворк изменит какое-то из перечислений, а у вас в приложении это не обработано (Так было, например с LABiometryType).

    В Swift 5 добавится атрибут unknown, который позволит разделить 2 различных сценария при обработке перечисления:

    • Код в default должен выполняться для всех случаев, не обработанных в switch
    • В switch обработаны все случаи, а если добавятся новые, то нужно использовать код в default

    Давайте посмотрим на примере:

    enum HTTPMethod {
        case post, get, put
    }
    
    // Без @unknown
    switch httpMethod {
    case .post: print("Post")
    case .get: print("Get")
    default: print("Put")
    }
    
    // С @unknown
    switch httpMethod {
    case .post: print("Post")
    case .get: print("Get")
    @unknown default: print("Unknown HTTP method")
    }
    

    Избавление от двойного Optional в результате вызова функции с try? (SE-0230)


    Наверняка многие сталкивались с тем, что при вызове throwable функции, возвращающей Optional, с помощью try?, в результате получался тип, завернутый в два Optional. Это не очень удобно и поэтому в Swift 5 вызов try? в таком случае вернет тип, завернутый только в один Optional.

    Вот так это было до Swift 5:

    let result = try? optionalObject?.foo() // type(of: result) = SomeType??
    

    А вот так будет в Swift 5:

    let result = try? optionalObject?.foo() // type(of: result) = SomeType?
    

    Проверка кратности (SE-0225)


    Для проверки кратности одного числа другому, можно использовать функцию isMultiple(of:), вместо остатка от деления (%):

    // Старый вариант
    let isEven = 4 % 2 == 0
    // Новый вариант
    let isEvent = 4.isMultiple(of: 2)
    

    Изменение незначительное, но делает код чуточку яснее и упрощает поиск по коду.

    Подсчет количества элементов в последовательности с условием (SE-0220)


    В Swift 5 у типа Sequence добавится метод count(where: (Element) -> Bool) -> Int, который позволит за один проход посчитать количество элементов в последовательности, удовлетворяющих заданному условию. До этого приходилось использовать filter в связке с count. Данный метод позволит сэкономить память, выделяемую при создании нового массива в методе filter.

    Пример:

    let countOfZeroes = [0, 1, 2, 0, 4].count(where: { $0 == 0 }) // countOfZeroes = 2
    

    Метод compactMapValues в Dictionary (SE-0218)


    Данный метод объединяет compactMap из Array и mapValues из Dictionary. В результате вызова этого метода создается словарь с трансформированными значениями, в котором нет значений равных nil.

    Пример:

    let dictionary = ["a": "1", "b": "2", "c": "Number"]
    let resultDictionary = dictionary.compactMapValues { Int($0) } // resultDictionary = ["a": 1, "b": 2]
    

    Raw strings (SE-0200)


    Добавлена возможность записывать строки, в которых кавычки и обратные слэши используются как обычные символы, а не как специальные. Для этого в начале и в конце строки надо добавить символ #.

    Пример:

    let string1 = #"Строка со словом "в кавычках""#
    let string2 = #"Строка с \обратным слэшем"#
    

    Если при создании строки используется вставка какой-либо переменной, то после обратного слэша надо добавить знак #:

    let string = #"Строка с переменной \#(variable)"#
    

    Если в строке присутствует знак #, то в начале и в конце строки надо добавить два знака ##:

    let string = ##"Строка со знаком #"##
    

    Протокол Sequence больше не содержит associated type SubSequence (SE-0234)


    Ассоциативный тип SubSequence был перенесен из протокола Sequence в Collection.Теперь все методы в Sequence, которые возвращали SubSequence, возвращают конкретный тип. Например, метод suffix теперь возвращает Array. Вот полный список методов, затронутых этим изменением:

    extension Sequence {
      public func dropFirst(_ k: Int = 1) -> DropFirstSequence<Self>
      public func dropLast(_ k: Int = 1) -> [Element]
      public func suffix(_ maxLength: Int) -> [Element]
      public func prefix(_ maxLength: Int) -> PrefixSequence<Self>
      public func drop(while predicate: (Element) throws -> Bool) rethrows -> DropWhileSequence<Self>
      public func prefix(while predicate: (Element) throws -> Bool) rethrows -> [Element]
      public func split(
        maxSplits: Int = Int.max,
        omittingEmptySubsequences: Bool = true,
        whereSeparator isSeparator: (Element) throws -> Bool
      ) rethrows -> [ArraySlice<Element>]
    }
    

    Теперь работать с этими методами станет проще.

    Ограничения для протоколов


    Теперь протоколы поддерживают ограничение в виде классов, реализующих этот протокол. Другими словами, теперь можно указать, что протокол может быть реализован только определенным классом. Например:

    protocol Viewable: UIView {}
    protocol Viewable where Self: UIView {}
    

    Второй вариант записи поддерживается в Swift 4.2, но может вызвать ошибку компиляции или в рантайме. В Swift 5 такой ошибки не возникнет.

    Заключение


    Это не весь список изменений в Swift 5, тут собраны только основные изменения. В общем и целом представленные изменения положительные и делают язык более понятным и гибким. Главное, чтобы «Convert to current Swift syntax» прошел безболезненно.

    На этом все, спасибо за прочтение.
    Tinkoff.ru
    211,00
    IT’s Tinkoff.ru — просто о сложном
    Поделиться публикацией

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

      +1
      Raw-строки никто так правильно и не сделал ни в одном языке. Здесь получается что это обычные escape-строки со спецсимволом «решетка» (а не обратный слэш или что там еще бывает).
      В моем понимании настоящая raw-строка это строка, перед которой задается произвольная пользовательская терминальная последовательность, далее идет строка, которая может содержать что угодно, пока не попадется еще раз эта терминальная последовательность.
        0

        Вы ищите что-то типа такого:


        Raw string literals do not process any escapes. They start with the character U+0072 ®, followed by zero or more of the character U+0023 (#) and a U+0022 (double-quote) character. The raw string body can contain any sequence of Unicode characters and is terminated only by another U+0022 (double-quote) character, followed by the same number of U+0023 (#) characters that preceded the opening U+0022 (double-quote) character.
        All Unicode characters contained in the raw string body represent themselves, the characters U+0022 (double-quote) (except when followed by at least as many U+0023 (#) characters as were used to start the raw string literal) or U+005C () do not have any special meaning.

        ?

          –2
          Ну да, типа такого. Только чтобы вместо # можно было использовать любую последовательность, определяемую пользователем (мало ли, что мы хотим сохранить в raw-строке — может там как раз много символов #).
          0

          HEREDOC из bash, perl, php?

            0
            фиксированная строка начинает и завершает raw-строку.
            А это явным образом противоречит высказыванию
            перед которой задается произвольная пользовательская терминальная последовательность
              0

              В php можно использовать произвольную последовательность:

                0
                если верить описанию — heredoc начинается с <<< + последовательность и заканчивается аналогичной последовательностью.
                + обратите внимание на ограничение символов закрывающей последовательности
          +2

          Синтаксический сахар — это конечно хорошо, но имхо, главное достижение пятой версии — Stable ABI. Это даёт заслуженную взрослость языку, который, как мне кажется, многие еще не воспринимают серьезно как раз из-за частых ломающих изменений.

            +3
            Я лично asyn/await жду и таки хотелось бы чтобы неймспейсы ввели нормальные а не через классы и экстеншены к ним пилить. Там как в C# например — одна папка = один неймспейс.
              +1
              Добавили тип Result (SE0235), что тоже очень удобно. Вот тут хороший туториал.

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

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