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


Привет, меня зовут Илья. Я — 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» прошел безболезненно.

На этом все, спасибо за прочтение.




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