В конце марта вышел релиз 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).

Источник