Swift 5.1 has been officially released for some time now, and although it is a minor release, it contains a number of changes and improvements. This week, let’s take a look at four of these features and where they might be useful.

A member initializer with default values

One of the many things that makes structures so attractive in Swift is that they have an automatically generated constructor for member variables. The member variable constructor, which allows us to initialize a structure (excluding privately stored properties) simply by passing values corresponding to each of its properties, as follows:

struct Message {
    var subject: String
    var body: String
}

let message = Message(subject: "Hello", body: "From Swift")
Copy the code

The auto-generated constructors have been significantly improved in Swift 5.1, as they now take default property values into account and automatically convert those values to default constructor parameters.

Suppose we extend the Message structure above to add a attachments and the body property, attachments has a default value of an empty array, and the body property has a default value of “” empty string:

struct Message {
    var subject: String
    var body = ""
    var attachments: [Attachment] = []}Copy the code

In Swift5.0 and earlier, to instantiate this structure, we must pass initialization parameters for all of the above properties, whether they have default values or not. However, in Swift5.1, we don’t need to do this, we can now initialize Message to just pass in a Subject, like this:

var message = Message(subject: "Hello, world!")
Copy the code

It’s really cool that we can initialize structures much more easily than before. But, perhaps even cooler, just as with standard default parameters, we can still override any default property by passing arguments to it, which provides a lot of flexibility:

var message = Message(
    subject: "Hello, world!",
    body: "Swift 5.1 is such a great update!"
)
Copy the code

However, although member initializers are useful in App or Module, they are not exposed as part of the Module’s public API, which means that if we create a Frameork or POD library, we still need to manually create constructors and add public.

Use Self to reference enclosing types

Previously, Swift’s Self keyword (which is really a type) enabled us to reference a type dynamically in a context where we did not know the specific type, for example, referencing the implementation type of a protocol in a protocol extension:

extension Numeric {
    func incremented(by value: Self = 1) -> Self {
        return self + value
    }
}
Copy the code

It still works, and Swift5.1 extends the use of Self to include concrete types (such as enumerations, structs, and classes), allowing us to alias Self as a type access method or property, like this:

extension TextTransform {
    static var capitalize: Self {
        return TextTransform{$0.capitalized }
    }

    static var removeLetters: Self {
        return TextTransform{$0.filter { !$0.isLetter } }
    }
}
Copy the code

We can now use the above Self instead of the full TextTransform type name, which is certainly a syntactic sugar, but it makes our code more compact, especially when dealing with long type names. We can even use Self inline in methods or attributes to make the above code even more compact:

extension TextTransform {
    static var capitalize: Self {
        return Self { $0.capitalized }
    }

    static var removeLetters: Self {
        return Self { $0.filter { !$0.isLetter } }
    }
}
Copy the code

In addition to referring to the type itself, we can now use Self to access static methods and properties, which is useful when we want to reuse the same value across all instances of the type. In the following example, use Self to access cellReuseIdentifier:

class ListViewController: UITableViewController {
    static let cellReuseIdentifier = "list-cell"

    override func viewDidLoad(a) {
        super.viewDidLoad()

        tableView.register(
            ListTableViewCell.self,
            forCellReuseIdentifier: Self.cellReuseIdentifier
        )
    }
}
Copy the code

Of course, we could simply use ListViewController on top when accessing static properties, but using Self does improve the readability of the code, and we don’t have to update how we access its static members when we rename the view controller.

The switch is optional

Next, let’s take a look at how Swift 5.1 makes it easier to perform pattern matching on optional objects. For example, suppose we are developing a music-class App that contains the Song model, which has an optional downloadState property:

struct Song {
    ...
    var downloadState: DownloadState?
}
Copy the code

The reason the above property is optional is that we want nil to indicate a lack of download status, that is, a song has not been downloaded at all.

Swift’s advanced pattern matching feature gives us direct access to an optional value – without unpacking it first, but, prior to Swift 5.1, doing so required us to add a question mark at the end of each match, as follows:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case.downloadInProgress? : showProgressIndiator(for: song)
    case .downloadFailed(leterror)? : showDownloadError(error,for: song)
    case.downloaded? : downloadDidFinish(for: song)
    case nil:
        break}}Copy the code

In Swift 5.1, those trailing question marks are no longer needed, and we can now refer to each case directly, just as we would when iterating over non-optional values:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case .downloadInProgress:
        showProgressIndiator(for: song)
    case .downloadFailed(let error):
        showDownloadError(error, for: song)
    case .downloaded:
        downloadDidFinish(for: song)
    case nil:
        break}}Copy the code

The difference between ordered sets

Finally, let’s take a look at the new standard library API, Ordered Collection Diffing, introduced as part of Swift 5.1. As tools like Combine and SwiftUI move closer to the world of declarative programming, being able to calculate the difference between two states becomes increasingly important. After all, declarative UI development is all about new snapshots of constantly rendering state.

For example, suppose we are building a Database Econtroller that allows us to easily update a database on disk with a Model in memory. To figure out whether to insert or delete the Model, we can now simply call the new difference(from:)API to calculate the difference between the old array and the new array, and then perform our database operations by iterating through the changes in that difference:

class DatabaseController<Model: Hashable & Identifiable> {
    private let database: Database
    private(set) var models: [Model] = []...func update(with newModels: [Model]) {
        let diff = newModels.difference(from: models)

        for change in diff {
            switch change {
            case .insert(_.let model, _):
                database.insert(model)
            case .remove(_.let model, _):
                database.delete(model)
            }
        }

        models = newModels
    }
}
Copy the code

However, the above implementation does not take into account the moved Model, because by default, moves are treated as separate inserts and deletes. To solve this problem, we also call the inferringMoves() method when calculating the difference, and then see if each insert is associated with a remove, and if so, treat it as a move, as shown below:

func update(with newModels: [Model]) {
    let diff = newModels.difference(from: models).inferringMoves()
    
    for change in diff {
        switch change {
        case .insert(let index, let model, let association):
            // If the associated index isn't nil, that means
            // that the insert is associated with a removal,
            // and we can treat it as a move.
            ifassociation ! =nil {
                database.move(model, toIndex: index)
            } else {
                database.insert(model)
            }
        case .remove(_.let model, let association):
            // We'll only process removals if the associated
            // index is nil, since otherwise we will already
            // have handled that operation as a move above.
            if association == nil {
                database.delete(model)
            }
        }
    }
    
    models = newModels
}
Copy the code

The fact that Difference (from:) is built into the standard library (as well as UIKit and AppKit) in Swift 5.1 is amazing because it’s so hard to write efficient, flexible and robust difference algorithms.