WWDC 2021 is over…

WWDC 2021 is over, and has brought a ton of new apis, tools improvements, in form of videos and labs to get to known them, but to be honest, for me, working full time, I barely watched three videos during the whole week, and there are so many videos, so many shinny things to look at…And not enough time, and that is fine, it is imposible to digest everything, and luckily, we got all recorded. So this is the content I’ve decided to digest during the time that it takes to do it, in headlines and organized for my interests, that may match yours…At the end, the goal of this article is to understand, that it takes time to assume new things, new knowledge, and as time is a limited resource, it is better to organize yourself, so there we go!

WWDC in headlines…for me.

Concurrency in Swift:

I can say with no fear to fail, that all the Swift community was very hipped with the coming of Async/Await to Swift, until realized it is constrained to iOS 15… Don´t get me wrong, it is an awesome language feature, but that practically in most of companies, we won’t be able to use at least in a couple of years…If you wanna feel the drama, there is big thread in the swift forums. Ok, stop whining and give the source. This are the videos I matter the most for this:

What is new in UI?

SwiftUI keeps growing, and by the time iOS 15 is released, having into account that will support devices as old as iPhone 6s, many projects will drop support for iOS 12 (this is an educated guess), and SwiftUI whether if you like it or not, is the future for UI:

Xcode got reinforcements!

I am a lucky man, and the company I work for (Agile Content), provided me with an M1 MacBook Pro to work with. I’m not bragging about it, just want to say that since I’m using a M1, most of the problems I had before working with Xcode are just gone, and now, with Xcode 13, our beloved/hated development tool has got very promising upgrades to ease our lives:

Those are a lot of seasons to watch, but wait…isn´t there any video about Combine? Well for second year in a row, nope, no videos, no updates, so, it’s Combine dead? That give for another post, but from my point of view, Combine is not dead, yet.

And that’s a wrap! The next article, as promised before, will be the introduction for combine!

Type Erasure

Type Erasure is heavily used in Combine Framework in order to hide chain publishers, and before we dive in Combine, is convenient to get use to know Type Erasure.

Contents of this article:

  • Getting to know type erasure
  • Conclusions

Getting to Know Type Erasure

Have you ever seen yourself trapped with this error when working with generics in protocols?

Compiler complaining when not filling the type working with associated types in protocols

Lets reproduce this source of frustration for many us the first time we faced it with an example. We have the requirement to create an accounting system to keep track of the expenses and incomes. Swift allows us to create incredibly flexible systems thanks to protocols, so we are going to create one protocol, Accountable, to define countability system:

protocol Accountable {
    associatedtype TransactionType
    func count(transaction: TransactionType, id: String)
}

The associatedType is the way to create a generic type in protocols, in this case, TransactionType, which will be the transaction, expense or income, and as well, a method to register the transaction in the hypothetic system. Now, we are going to create a protocol to define what a transaction is:

protocol Transaction {
    var value: Float { get set }
    var date: Date { get set }
    var id: String { get set }
}

Easy, a transaction must consist in a value with type float, the date when the transaction happened, and an id. Done with the blueprints, lets create the system:

struct Income: Transaction {
    var value: Float
    var date: Date
    var id: String
}

struct Accounter: Accountable {
    func count(transaction: Transaction, id: String) {
        // Register income
    }
}

We have defined the struct Income that implements Transaction, and another struct named Accounting, that implements Accountable, that will take care of the registration of incomes.

let income = Income(value: 10.0, date: Date(), id: UUID().uuidString)
let accounter: Accountable = Accounter()
accounter.count(transaction: income, id: income.id)

Unfortunately, when defining accounter, we got the ominous error message:

Protocol ‘Accountable’ can only be used as a generic constraint because it has Self or associated type requirements

What does means this message? An associated type is a placeholder for a type in a protocol, and we have to fill the placeholder, with a type, just using an instance of type that implements the protocol is not enough information for the compiler in order to work. How can we solve this? The message is already giving us a hint, which is one of the most used ways of type erasures, working with a generic wrapper.

class AnyAccounter<TransactionType>: Accountable {
    private let counTransaction: (TransactionType, String) -> Void

    init<Account: Accountable>(anyAccounter: Account) where Account.TransactionType == TransactionType {
        self.counTransaction = anyAccounter.count
    }

    func count(transaction: TransactionType, id: String) {
        counTransaction(transaction, id)
    }
}

Uh oh! Thats quite a daunting pice of code if you are not use to work with generics. Why do we need this, can we just move forward using a concrete type? Yeah, well, we could, but then we loose all the power that generics protocols can provide us to create generic apis, and type erasure enable use to work with generic protocols that have requirements that are specific to a type. Lets decipher AnyAccounter class:

AnyAccounter class has a generic constraint of type TransactionType, implements Accountable protocol, and its initializer is constrained to a generic type Account that has to implement the Accountable protocol, and as well, it accepts the parameter of generics type Account, making sure that the generic type associated type is of the same type. Take your time, I had to take as well in order to wrap around my head this idea, and still it is a little bit confusing.

Summarizing, AnyAccounter works as a box that wraps around the type (hiding or erasing it) that will work with the Accountable protocol, giving a reference to the compiler so the type safety is not broken. Lets finish the example, it will be clearer this way:

let income = Income(value: 10.0, date: Date(), id: UUID().uuidString)
let incomeCounter = Accounter()
let accounting = AnyAccounter<Transaction>(anyAccounter: incomeCounting)
accounting.count(transaction: income, id: income.id)

So, have created an income transaction, and using the previously defined Accounter, have instantiated it, AND, instantiated as well AnyAccounter, providing the Associated type, which in this case in Transaction, and passing the instance of Accounter, incomeCounter to AnyAccounter. This type we are not getting the annoying error message, and can register our income earned with sweat, trying to understand Type Erasure.

Conclusion

The above example only copes one of the cases, but there are many others to deal with type erasure, and I invite you to keep investigating it, as it is heavily used in Combine Framework, which will be introduced in the next article!

Comments, typos, errors?

Please, if you see any error, typo, way to improve it, or just one to say Hi, don’t hesitate to contact!

Keypaths

Keypaths has been around for a while, and in Swift 5.2 got an upgrade which will help us to work together with SwiftUI and Combine, so let’s take a look and learn a little bit more.

Contents of this article:

  • What are Keypaths for?
  • Working with Keypaths
  • Conclusions

What are Keypaths

Keypaths are references to a property of a type, that allow to work with them afterwards. Keypaths are read only, if we would like to read and write form the resulting value, there are two other types:

WritableKeyPath: Specific for value types, in which case, the accessed property has to be specified as mutable.

ReferenceWritableKeyPath: Same as WritableKeyPath but specific for reference types.

Working with Keypaths:

Nothing better than examples to wrap one´s head around.

So we have a type Movie, and we would like to access the unique identifier of the movie in our catalog, and work with the uuid afterwards.

struct Movie {
    let uuid: String
    let isLive: Bool
    let needsSuscription: Bool
}

let keyPathToUUID = \Movie.uuid

// Work with keypath

let movie = Movie(uuid: "yoMovie", isLive: false, needsSuscription: true)

// Read value

print (movie[keyPath: keyPathToUUID]) // Will print "yoMovie"

keyPathToUUID it is actually of type AnyKeyPath, which means it is a key path rooted on Any type to Any value, so when we reference the type from where they Keypaths is rooted, and the property rooted, it would be written like this:

let keyPathToUUID: AnyKeyPath = \Movie.uuid

It is possible to access the key path to the property, by referencing the type, like this:

let keyPathToUUID : PartialKeyPath<Movie> = \.uuid

If we would like to change the value, by declaring the instance as var and the properties as var, the Keypath would be of type WritableKeyPath :

struct Movie {
    var uuid: String
    let isLive: Bool
    let needsSuscription: Bool
}

var keyPathToUUID = \Movie.uuid

// Work with keypath

var movie = Movie(uuid: "yoMovie", isLive: false, needsSuscription: true)

// Write value

movie[keyPath: keyPathToUUID] = "daMovie"

print (movie[keyPath: keyPathToUUID]) // Will print "daMovie"

What if movie is a reference type?

class Movie {
    var uuid: String = "theMovie"
}

let keyPathToUUID = \Movie.uuid

// Work with keypath

let movie = Movie()

// Write value

movie[keyPath: keyPathToUUID] = "daMovie"

print (movie[keyPath: keyPathToUUID]) // Will print "daMovie"

This time, although Movie has been declared as let (but the property is declared as var), as the Movie is a class, it is possible to write the value of the property through the keypath.

Alright then, but what are they useful for? Can we just assign value to properties as usual and move on? Yeah, we could do that, but as we said per definition, Keypaths are a reference to a property of a type, and we don’t need an instance of this type to work with, and that is very powerful!

Lets say we have a screen where we would like to show Movie details, but in our catalog, we have as well Series, that shares some properties with Movies, and we would like to show the shared details in one screen:

struct Movie {
    var uuid: String
    var title: String
    var synopsis: String
    var needsSuscription: Bool
}

struct Series {
    var uuid: String
    var title: String
    var synopsis: String
    var isLive: Bool
    var needsSuscription: Bool
}

// Quick uiview for helping porpuses

class ContentDetailsView: UIView {
    let titleLabel = UILabel()
    let synopsisLabel = UILabel()
}

struct ViewConfigurator<Content> {
    let titleKeyPath: WritableKeyPath<Content, String>
    let synopsisKeyPath
        : WritableKeyPath<Content, String>
    
    func configureView(view: ContentDetailsView, content: Content) {
        view.titleLabel.text = content[keyPath: titleKeyPath]
        view.synopsisLabel.text = content[keyPath: synopsisKeyPath]
    }
}

let movieDetailConfigurator = ViewConfigurator<Movie>(
    titleKeyPath: \.title,
    synopsisKeyPath: \.synopsis
)

let seriesDetailConfigurator = ViewConfigurator<Series>(
    titleKeyPath: \.title,
    synopsisKeyPath: \.synopsis
)

Pretty neat right? This piece of code is inspired in an example gotten from SwiftBySundell.com, I liked it so much, and showed so well the beauty of Keypaths, that decided to base the main example of this article on it.

And last, but not least, in Swift 5.2 Keypaths got and upgrade, that is that we can pass them around as functions, like this:

let movies = [theMovie, yoMovie, daMovie]

let moviesSynopsis = movies.map(\.synopsis)

And this is how we usually would do without using Keypaths:

let moviesSynopsisOldWay = movies.map { $0.synopsis }

The general rule to pass Keypaths as functions is the function has to be to type (Root) -> Value, which means that Keypath we pass has to be able to extract the keypaths value and the returned value is valid.

Conclusion

In the way to explore SwiftUI and Combine, we have given another step forward setting the foundational knowledge, this time working with Keypaths, which it used a lot in SwiftUI and Combine. Next stop Type Erasures!

Property wrappers

In the way up of discovering SwiftUI and Combine, we will have to get along with property wrappers. Property wrappers was a language feature introduced in Swift 5.1, a feature that would be heavily used for SwiftUI and Combine. 

Contents of this article:

  • What is a property wrapper?
  • Anatomy of property wrappers
  • Limitations of property wrappers
  • Conclusions

What is a property wrapper?

As per documentation, a property wrapper is a layer of separation between code that manages how a property is stored and the code that defines a property . Initially, property wrappers were named property delegates , so we can think about property wrappers as a property that delegates its get and set to another type. The main goal, is to reuse implementation patterns in a easy way, hence reducing boiler plate code.

Anatomy of property wrappers

In order to understand the anatomy of property wrappers, let’s first see an implementation of one of the typical implementation pattern in use, transforming a value, but without using property wrappers. This old style wrapper provides as with the possibility to get capitalized text.

// Old style wrapper
struct CapitalizeString {
    private var _text: String
    init(text: String) {
        _text = text
    }
    var text: String {
        get {
            return _text.capitalized
        }
        set {
            _text = newValue
        }
    }
}

This wrapper, can be used like this:

struct TextToCapitalize {
    var text: CapitalizeString
}

let capitalizeString = CapitalizeString(text: "")
var text = TextToCapitalize(text: capitalizeString)
text.text.text = "house"
print(text.text.text)
"House"

What do we need in order to create a property wrapper and improve the old way? When declaring the struct, enum or class, will be preceded by the keyword @propertyWrapper, and a wrappedValue property, as follow:

@propertyWrapper
struct CapitalizeStringWrapper {
    private var text: String
    init(wrappedValue: String)  {
        self.text = wrappedValue
    }
    var wrappedValue: String {
        get {
            text.capitalized
        }
        set {
            text = newValue
        }
    }
}

And it would be used like this:

struct NameToCapitalize {
    @CapitalizeStringWrapper var name: String
}

var name = NameToCapitalize(name: "iker")
print(name.name)
"Iker"

Much cleaner and straight forward, don’t you think? Sweet syntactic sugar.

Additionally, we can expose functionality of the property wrapper through the definition of a projected value. Following the CapitalizeStringWrapper example we could add a function to uppercase the text, and expose it with a projected value:

@propertyWrapper
struct CapitalizeStringWrapper {
    private var text: String
    
    var projectedValue: CapitalizeStringWrapper { return self }
    
    init(wrappedValue: String)  {
        self.text = wrappedValue
    }

    var wrappedValue: String {
        get {
            return text.capitalized
        }
        set {
            text = newValue.capitalized
        }
    }
    
    func uppercase() -> String {
        return self.text.uppercased()
    }
}

In order to access this new functionality, we use dollar sing to access the wrapper projection:

struct TextToUpperCase {
    @CapitalizeStringWrapper var name: String
}

var name = TextToUpperCase(name: "iker")
print(name.$name.uppercase())
"IKER"

With the projected value, we enable to access functionality from outside, but it is also possible to access it from inside the type that use the property wrapper like this:

struct TextToUpperCase {
    @CapitalizeStringWrapper var name: String
    
    func uppercase() {
        _name.uppercase()
    }
}

Since Swift 5.4, which will be the default Swift version in the upcoming release of Xcode 12.5, it is possible to use a property wrapper as a local variable like this:

struct TextToUpperCase {
    @CapitalizeStringWrapper var name: String
    
    func uppercase() {
        @CapitalizeStringWrapper var upperCasedName = "iker"
    }
}

Limitations of property wrappers:

While property wrappers might be very helpful, and a must in the future of Swift, it has its downsides:

  • A property wrapper cannot be declared in a protocol or an extension (Ouch!).
  • We cannot use typealias with property wrappers (Too much syntactic sugar may be?)
  • Property wrappers are difficult to compose.
  • It is not possible to override properties with wrapper properties.

Conclusion

As seen in this article, property wrappers helps us to reduce boiler plate, but as well can make the code more difficult to understand. Wether if we like it or not, it is a fact that we have to get use to it to understand SwiftUI mostly and Combine.

And that’s a wrap!

Comments, typos, errors?

Please, if you see any error, typo, way to improve it, or just one to say Hi, don’t hesitate to contact!

Opening

Dawn at arenales del sol

It’s been a while since started to think about creating a programming blog about iOS and Swift, yet always found an excuse, too much work, family time, need for sleep… and the Swift world evolves so fast, and waits no one! So, no more excuses!

This blogs aims to be a tool to help others and myself to keep up with fast pace Swift language evolution and the myriad of technologies that revolves around. So let’s begin!