Привет, меня зовут Илья. Я — 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» прошел безболезненно.
На этом все, спасибо за прочтение.
Автор: NoFearJoe