1

Since any and some both apply to protocols, I wanted to compare them together in this blog post to better explain what problem they solve respectively, and in When to use any , some or others.

Understand the problems that any and some solve

In order to explain the problem solved by any , we can understand these two keywords through a lizi. Here is the protocol for a Pizza model:

 protocol Pizza {
    var size: Int { get }
    var name: String { get }
}

In Swift 5.6, you might write the following method to receive a Pizza

 func receivePizza(_ pizza: Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

When this function is called, receivePizza the function receives a so-called pizza protocol type, which we can understand as a pizza box. In order to know the pizza name, you must open the box, that is, get the concrete object that implements the Pizza protocol, and then get the name. This means that Pizza has almost no compile-time optimizations, which makes receivePizza method calls more expensive than we would like.

In addition, the following function looks like the same

 func receivePizza<T: Pizza>(_ pizza: T) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

However, there is a very major difference here. Pizza protocol is not used as parameter type here. It is used as a constraint on the generic T. This will allow the compiler to resolve the type of T at compile time, so that receivePizza receives a reified type.

Because the difference between the above two methods is not very clear, the Swift team introduced the any keyword. This keyword does not add any new functionality. It forces us to clearly convey "this is an existentialism": (It's a bit of a mouthful, and it's not easy to understand, so I understand him as 这么类型的一个东西 )

 // 上面的第一种写法,增加一个any关键字
func receivePizza(_ pizza: any Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

The example using the generic T doesn't need the any keyword because Pizza is used as a constraint rather than an existential type.

Now that we have a clearer understanding of any , let's move on to some .

In Swift, many developers write code like this:

 let someCollection: Collection

We would get compiler errors telling us Collection that there is a type requirement for Self or association. In Swift 5.1, we can tell the compiler that anyone accessing someCollection shouldn't care about this. They should just need to know that this thing conforms to the Collection protocol, and that's it.

This mechanism is critical to enabling SwiftUI's View protocol.

But there is also a disadvantage, that is, when using some Colelction , it is impossible to know what the associated type is.

However, not all protocols have associated association types. Consider again the following receivePizza version:

 func receivePizza<T: Pizza>(_ pizza: T) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

We define a generic T to allow the compiler to optimize for a given concrete type of Pizza. The some keyword also allows the compiler to know at compile time what the underlying actual type of the some object is; it's just hidden from us. That's exactly what <T: Pizza> does. We can only access the content exposed by the Pizza protocol through the T type. This means we can rewrite receivePizza<T: Pizza>(_:) as follows:

 func receivePizza(_ pizza: some Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

We don't need T anymore, which means we don't need to create a type to represent our Pizza. We can say "this function needs some Pizza " instead of "this function needs some Pizza which we call T". The two writings are equivalent.

choose some or any

In fact, when we understand some and any, we will know that it is not a problem to choose one of them, they all solve their own problems.

Generally speaking, we want to use some or generics as much as possible. Take our Pizza as an example. If we use any, it is like we will also receive a Pizza type box at runtime, specifically what Pizza, and requires us to open the box again, but some or generics, will give us an actual Pizza type.

practice

Let's illustrate this with another example that draws heavily from my explanation of the main association types.

 class MusicPlayer {
    var playlist: any Collection<String> = []

    func play(_ playlist: some Collection<String>) {
        self.playlist = playlist
    }
}

In this code, I used some Collection<String> instead of writing func play<T: Collection<String>>(_ playlist: T) because generics are only used in this one place.

Mine var playlist is any Collection<String> and not some Collection<String> for two reasons:

  1. There is no guarantee that the concrete Collection that the compiler deduces for the play method matches the concrete Collection deduced for the var playlist; that means they may not be the same.
  2. The compiler can't infer var playlist:some Collection<String> in the first place (try it, you'll get a compiler error)

We can avoid using any by writing:

 class MusicPlayer<T: Collection<String>> {
    var playlist: T = []

    func play(_ playlist: T) {
        self.playlist = playlist
    }
}

But this will force our T to be of the same type. For example, when we use T as Array, we cannot pass in other Collection types, such as Set, in the play method. But the previous way of writing is possible.

Summarize

While some and any sound complicated (and they are), they are also a very powerful and important part of Swift 5.7. Understanding them is necessary because it helps us better understand how Swift handles generics and protocols.


Sunxb
83 声望330 粉丝