Greetings, traveler!
The ‘some’ and ‘any’ keywords are familiar to most Swift developers. The ‘some’ keyword was introduced in Swift 5.1, and the ‘any’ keyword was introduced in Swift 5.6. Finally, in Swift 5.7, Apple allowed us to use these keywords as the function’s parameters.
So, what’s in it for us? One practical benefit is that we can now eliminate an annoying error.
Protocol ‘Type’ can only be used as a generic constraint because it has Self or associated type requirements.
But this is not the only advantage, of course. Let’s look at how one keyword differs from another and how it can help us to handle generic protocols. We’ll also determine how to use these keywords to improve the application’s performance.
Generic protocols
Swift protocols can use generics to define associated types, which enables us to create type-safe abstractions. Let’s start by making one.
protocol iPhoneProtocol {
associatedtype AuthKind
func authenticate(with authKind: AuthKind)
}
Let’s say we have an iPhone 8 and an iPhone 15. The difference is that one uses Touch ID for authentication, while the other uses Face ID.
struct FaceID {
var name = "FaceID"
}
struct TouchID {
let name = "TouchID"
}
Now, let’s apply this protocol to concrete objects. We can handle this situation via our generic protocol.
struct iPhone8: iPhoneProtocol {
func authenticate(with authKind: TouchID) {
print("Authenticate via \(authKind.name)")
}
}
struct iPhone15: iPhoneProtocol {
func authenticate(with authKind: FaceID) {
print("Authenticate via \(authKind.name)")
}
}
Can we use such a protocol as a function parameter? Yes, but not right away. This code won’t compile.
func call(via iPhone: iPhoneProtocol) {
// do something...
}
And this code will successfully compile.
func call<T: iPhoneProtocol>(via iPhone: T) {
// do something
}
You can also write this method a little differently, which will not affect its functionality.
func call<T>(via iPhone: T) where T: iPhoneProtocol {
// do something
}
Since Apple introduced these new Swift features, we can use the ‘some’ and the ‘any ‘keywords to write the same function.
func call(via iPhone: some iPhoneProtocol) {
// do something
}
func call(via iPhone: any iPhoneProtocol) {
// do something
}
Differences between the ‘some’ and the ‘any’ keywords
Let’s look at the differences between these keywords. When we use the ‘some’ keyword, the compiler sees that as an opaque type, in other words, something that conforms to a concrete protocol.
The ‘any’ keyword indicates that any type can satisfy a protocol, and the type that conforms to the protocol is not known until runtime. The ‘any’ keyword relies on a Type Erasure mechanism and hides specific type information that is not important at the moment.
Let’s take an example to make it easier to understand. Let’s create two variables that are iPhoneProtocol type. For the first time, we will use the ‘some’ keyword.
var first: some iPhoneProtocol = iPhone8()
var second: some iPhoneProtocol = iPhone15()
Then, let’s try assigning the value of one variable to another. It won’t work.
second = first // ❌ Compile error: Cannot assign value of type 'some iPhoneProtocol' (type of 'first') to type 'some iPhoneProtocol' (type of 'second')
The fact is that this opaque type can only be processed by the compiler in a variable scope. In other words, you cannot assign another concrete value after declaring this variable.
Now, replace the keyword with the ‘any’ keyword.
var first: any iPhoneProtocol = iPhone8()
var second: any iPhoneProtocol = iPhone15()
The compilation was successful.
second = first // ✅ Compiled successfully
Moreover, the ‘some’ keyword allows collections with only similar conforming type inside. The ‘any’ keyword allows collections with any conforming type inside.
Check out this code.
// ❌ Compile error: Conflicting arguments to generic parameter 'τ_0_0' ('iPhone8' vs. 'iPhone15')
var someArray: [some iPhoneProtocol] = [
iPhone8(),
iPhone15()
]
// ✅ Compiled successfully
var someArray: [some iPhoneProtocol] = [
iPhone8(),
iPhone8()
]
// ✅ Compiled successfully
var anyArray: [any iPhoneProtocol] = [
iPhone8(),
iPhone15()
]
The same applies to the return type: while the ‘some’ keyword allows only the same conforming type to be returned, the ‘any’ keyword allows any conforming type.
var is15 = true
// ❌ Compile error: Function declares an opaque return type 'some iPhoneProtocol', but the return statements in its body do not have matching underlying types
var iPhone: some iPhoneProtocol {
if is15 {
return iPhone15()
} else {
return iPhone8()
}
}
// ✅ Compiled successfully
var iPhone: any iPhoneProtocol {
if is15 {
return iPhone15()
} else {
return iPhone8()
}
}
When should the ‘some’ keyword be used
While using the keyword ‘any’ may seem more convenient, it does have its own costs. The existential types (the ‘any’ keyword) are less efficient than the opaque types (the ‘some’ keyword) because the compiler won’t know the actual memory usage. Since this is the case, I recommend checking if the keyword ‘some’ can be used first and considering using the keyword ‘any’ only if it is difficult or impossible.
Check out other posts:
- How to change the order of Calendar weekdaySymbols
- Mutex in Swift 6 and Synchronization
- Exploring the take() method for Optionals
- How to display an icon with a title using the Label in SwiftUI
- How to dysplay byte counts in Swift
- How to format date in SwiftUI
- How to use Hierarchical Colors in SwiftUI
- Global actors and Singletons
- How to organize layout with ControlGroup in SwiftUI