Swift 5.2. Overview of all changes

At the end of March, Swift 5.2 was released for Xcode 11.4. It has improved error diagnosis, dependency analysis, and expanded SwiftPM functionality. An overview of some changes has already been published on Habré , in the same article the evolution of the language itself with possible examples of use is considered.


 

SE-0249 KeyPath as a function


Shortcut puts keyPath into a function, where the input parameter is the object itself, and the result is its property.

Let's look at an example. Create an array of simple models:

struct Model {
    let isHidden: Bool
}
 
let modelArray = [Model(isHidden: true), Model(isHidden: false)]

Filter the array by the isHidden property. Below are 3 examples with the same result:

// 1
let funcIsHidden: (Model) -> Bool = \.isHidden
modelArray.filter(funcIsHidden)
 
// 2
modelArray.filter(\.isHidden as (Model) -> Bool)
 
// 3
modelArray.filter(\.isHidden)

The declared functionality does not work in the following example:

//   
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)

Also, unlike keyPath, autocomplet does not work.

It is convenient to use for working with arrays in functions such as filter, map, reduce, sort, and the like.

SR-6118 Subscripts May Contain Default Settings


All function parameters can be set to the default value. Create a Box structure that contains an array of elements, and a subscript function to access them.

struct Box {
    let items: [String]
    
    subscript(_ index: Int = 0) -> String {
        items[index]
    }
}

Now, to access the first element, you can omit the index:

let box = Box(items: ["laptop, , mobile phone"])
let firstItem = box[] // "laptop"

SR-2189 Local Functions Support Default Parameters from External Visibility


In everyday practice, local functions are rarely used. It can be difficult to justify their use. Nevertheless, I admit that in certain cases this may come in handy.

For an example, we will create a function inside which we will describe a local one:

func getXPosition() {
    func calculateWidth(x: CGFloat = minX) -> CGFloat { ... }
    let minX: CGFloat = 0
    ...
}

As the default parameter for the calculateWidth function, we can use the values ​​within the getXPosition function.

SE-0253 Using Values ​​as Functions


Functionality, similar to @dynamicCallable, allows you to use a value as a function. But it is an implementation of the "static call".

In practice, everything is extremely simple: to call functions, you can refer to values ​​as methods.

Create a Player structure:

struct Player {
    private(set) var isRunning = false
    
    mutating func callAsFunction() {
        isRunning.toggle()
    }
}

Create an instance of Player and refer to it as a function:

var player = Player()
print(player.isRunning) // false
player()
print(player.isRunning) // true

At the same time, it is forbidden to cast, and therefore, pass an object as a function:

// ERROR: Cannot convert value of type 'Player' to type '() -> Void' in coercion
let playerAsFunc = player as () -> Void

You can add as many methods as callAsFunction to a class, structure, or protocol:

extension Player {
    func callAsFunction(isRunning: Bool = false) throws { ... }
    func callAsFunction() -> Bool { ... }
}

Application is possible in values ​​that are mathematical functions, complex expressions or in cases where the value has one dominant function. Nevertheless, you should not abuse this functionality, as it can be misleading.

SR-4206 Fixed bug with function overriding with generic parameter


Now, overriding the function, you cannot change or add restrictions on the generic type. As an example, create a CarBuilder class and inherit from it by overriding the add method:

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 Extending a protocol without class restrictions inherits this restriction


In the case of restrictions on Self, the extension will apply the restriction.

For example, create the Menu and extension protocol with a class restriction:

protocol Menu {}
 
class SideMenu: Menu {
  var sectionHeight = 0
}
 
extension Menu where Self: SideMenu {
  var menuHeight: Int {
    get { sectionHeight * 20 }
    set { sectionHeight = newValue / 20 }
  }
}

At the same time, the setter is nonmutating, as if the protocol has a class restriction.

SR-11429 Casting for functions with labels


When casting functions to type labels are now removed. Thanks to this, you can cast functions with labels. Previously, this functionality worked only in functions without labels:

func setX(x: Int) {}
(setX as (Int) -> Void)(5)

However, the default values ​​will not be saved:

func setPoint(x: Int, y: Int = 0) {}
(setPoint as (Int, Int) -> Void)(5, 1)

This also applies to generics, so this code is no longer valid.

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 The filter (_ :) function is called in the expected order in lazy collections


Create a lazy collection and call the count method:

let lazyArray = [0]
    .lazy
    .filter { _ in print("A"); return true }
    .filter { _ in print("B"); return true }
 
//  A B
_ = lazyArray.count

Before the result of the call was the opposite: B A.

SR-2790 A number of type initializers of the UnsafePointer / UnsafeBufferPointer family now issue a warning


The restriction applies to strings, arrays, and inout parameters that do not exist outside of the function call.

Create a structure that takes UnsafePointer as a parameter during initialization:

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: "")
}

There were 9 changes in this version, however, they definitely introduced new functionality. I assume that the most used of the current ones will be KeyPath as a function.

And we look forward to the next version. Apparently, it will support Swift on Windows and interesting features will appear, such as the use of self in escaping closures without accessing through self. * ( SE-0269 ) and the functionality of generics ( SE-0267 ) will expand .

Source

All Articles