Swift 5.2. Обзор всех изменений

от автора

В конце марта вышел релиз Swift 5.2 для Xcode 11.4 beta. В нём улучшена диагностика ошибок, анализ зависимостей, расширен функционал 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).

Источник

ссылка на оригинал статьи https://habr.com/ru/company/funcorp/blog/496178/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *