В конце марта вышел релиз Swift 5.2 для Xcode 11.4. В нём улучшена диагностика ошибок, анализ зависимостей, расширен функционал SwiftPM. Обзор некоторых изменений уже был опубликован на Хабре, в этой же статье рассмотрена эволюция самого языка с возможными примерами использования.
SE-0249 KeyPath как функция
Шорткат кастит keyPath в функцию, где входящий параметр — сам объект, а результат — его свойство.
Давайте смотреть на примере. Создадим массив из простых моделей:
struct Model {
let isHidden: Bool
}
let modelArray = [Model(isHidden: true), Model(isHidden: false)]
Отфильтруем массив по свойству isHidden. Ниже приведены 3 примера с одинаковым результатом:
// 1
let funcIsHidden: (Model) -> Bool = .isHidden
modelArray.filter(funcIsHidden)
// 2
modelArray.filter(.isHidden as (Model) -> Bool)
// 3
modelArray.filter(.isHidden)
Заявленная функциональность не работает в следующем примере:
// Валидный код
let selfFunc: (Model) -> Model = .self
modelArray.compactMap(selfFunc)
// ERROR: Expression type '(Model) -> Model' is ambiguous without more context
modelArray.compactMap(.self as (Model) -> Model)
// ERROR: Key path value type 'Optional<_>' cannot be converted to contextual type '_'
modelArray.compactMap(.self)
Также, в отличие от keyPath, не работает автокомплит.
Удобно использовать для работы с массивами в таких функциях, как filter, map, reduce, sort и аналогичных.
SR-6118 Subscripts может содержать параметры по умолчанию
Всем параметрам функции можно задать значение по умолчанию. Создадим структуру Box, которая содержит массив элементов, и функцию subscript для обращения к ним.
struct Box {
let items: [String]
subscript(_ index: Int = 0) -> String {
items[index]
}
}
Теперь для обращения к первому элементу можно опустить индекс:
let box = Box(items: ["laptop, , mobile phone"])
let firstItem = box[] // "laptop"
SR-2189 Локальные функции поддерживают параметры по умолчанию из внешнего предела видимости
В повседневной практике локальные функции используются редко. Оправдать их использование бывает сложно. Тем не менее, допускаю, что в определённых случаях это может пригодиться.
Для примера создадим функцию, внутри которой опишем локальную:
func getXPosition() {
func calculateWidth(x: CGFloat = minX) -> CGFloat { ... }
let minX: CGFloat = 0
...
}
В качестве параметра по умолчанию для функции calculateWidth можем использовать значения в границах функции getXPosition.
SE-0253 Использование значений в качестве функций
Функциональность, аналогично @dynamicCallable, позволяет использовать значение в качестве функции. Но является реализацией «статического вызова».
На практике всё крайне просто: для вызова функций можно обращаться к значениям как к методам.
Создадим структуру Player:
struct Player {
private(set) var isRunning = false
mutating func callAsFunction() {
isRunning.toggle()
}
}
Создадим экземпляр Player и обратимся к нему как к функции:
var player = Player()
print(player.isRunning) // false
player()
print(player.isRunning) // true
При этом запрещено кастить, а значит, и передавать объект как функцию:
// ERROR: Cannot convert value of type 'Player' to type '() -> Void' in coercion
let playerAsFunc = player as () -> Void
Можно добавить сколько угодно методов с названием callAsFunction к классу, структуре или протоколу:
extension Player {
func callAsFunction(isRunning: Bool = false) throws { ... }
func callAsFunction() -> Bool { ... }
}
Применение возможно в значениях, которые представляют собой математические функции, сложные выражения или же в случаях, где значение имеет одну доминирующую функцию. Всё же не стоит злоупотреблять данным функционалом, так как он может вводить в заблуждение.
SR-4206 Исправлен баг с переопределением функции с generic-параметром
Теперь, переопределяя функцию, нельзя изменить или добавить ограничения на generic-тип. В качестве примера создадим класс CarBuilder и наследуемся от него, переопределив метод add:
protocol PowerfullEngine { }
class CarBuilder {
func add<T>(engine: T) {}
}
class MercedesBuilder: CarBuilder {
// Валидный код
override func add<T>(engine: T) {}
// ERROR: Overridden method 'add' has generic signature <T where T : PowerfullEngine> which is incompatible with base method's generic signature <T>; expected generic signature to be <T>
override func add<T: PowerfullEngine>(engine: T) {}
}
SR-11298 Расширение протокола без ограничений на класс наследует это ограничение
В случае наложения ограничения на Self, расширение применит ограничение.
К примеру, создадим протокол Menu и extension с ограничением по классу:
protocol Menu {}
class SideMenu: Menu {
var sectionHeight = 0
}
extension Menu where Self: SideMenu {
var menuHeight: Int {
get { sectionHeight * 20 }
set { sectionHeight = newValue / 20 }
}
}
При этом сеттер — nonmutating, как будто протокол имеет ограничение на класс.
SR-11429 Кастинг для функций с лейблами
При кастинге функции к типу лейблы теперь убираются. Благодаря этому можно кастить функции с лейблами. Прежде данная функциональность работала только в функциях без лейблов:
func setX(x: Int) {}
(setX as (Int) -> Void)(5)
При этом значения по умолчанию сохранить не выйдет:
func setPoint(x: Int, y: Int = 0) {}
(setPoint as (Int, Int) -> Void)(5, 1)
Это применимо и к generic-ам, поэтому данный код больше не валиден.
typealias Point<T> = T
func set(x: Int) {}
// Валидный код
(set as Point)(5)
// ERROR: Extraneous argument label 'x:' in call
(set as Point)(x: 5)
SR-11841 Функция filter(_:) вызывается в ожидаемом порядке в lazy-коллекциях
Создадим lazy-коллекцию и вызовем метод count:
let lazyArray = [0]
.lazy
.filter { _ in print("A"); return true }
.filter { _ in print("B"); return true }
// Результат A B
_ = lazyArray.count
Прежде результат вызова был обратный: B A.
SR-2790 Ряд инициализаторов типов семейства UnsafePointer / UnsafeBufferPointer теперь выдаёт предупреждение
Ограничение действует на строки, массивы и inout-параметры, которые не существуют за пределами вызова функции.
Создадим структуру, принимающую UnsafePointer в качестве параметра при инициализации:
struct Game {
let info: UnsafePointer<Int8>
}
func createGame() {
var i: Int8 = 0
// WARNING: Initialization of 'UnsafePointer<Int8>' results in a dangling pointer
_ = Game(info: .init(&i))
// WARNING: Passing '[Int8]' to parameter, but argument 'character' should be a pointer that outlives the call to 'init(character:)'
_ = Game(info: [1, 2])
// WARNING: Passing 'String' to parameter, but argument 'character' should be a pointer that outlives the call to 'init(character:)
_ = Game(info: "")
}
Всего изменений в этой версии было 9, тем не менее, они определённо внесли новую функциональность. Предполагаю, что самым используемым из текущих будет KeyPath как функция.
А мы с нетерпением ждём следующую версию. Судя по всему, в ней будет поддержка Swift на Windows и появятся интересные фичи, такие как использование self в escaping-замыканиях без обращения через self.* (SE-0269) и расширится функциональность generic-ов (SE-0267).
Автор: Даниил