I had a look at the thread you're referring to. It seems nobody really spelled this out for the OP there, but the issue with the code he mentions is that [EventProtocol]
implies [any EventProtocol]
.
The any
part of that declaration means the array no longer contains just a type that conforms to EventProtocol
, but it contains any number of possible things that conform to the protocol. The flexibility required to do that also comes with some (significant) overhead.
To fix the performance in the linked post's example, simply add some
to the EventProtocol
array definition:
protocol EventProtocol {
var a: Int { get }
var b: Int { get }
}
struct Event: EventProtocol {
let a = 0
let b = 1
}
// here:
let array: [some EventProtocol] = Array(repeating: Event(), count: 1_000_000_000)
for (i, element) in array.enumerated() {
callback(i) { event in
if event.a != 0 {
preconditionFailure()
}
}
}
func callback(_ index: Int, block: (_ event: EventProtocol) -> Void) {
block(array[index])
}
If you want to be more flexible with this, you could put the "work" inside a function:
protocol EventProtocol {
var a: Int { get }
var b: Int { get }
}
struct Event: EventProtocol {
let a = 0
let b = 1
}
let array = Array(repeating: Event(), count: 1_000_000_000)
// here:
func doWork(_ array: [some EventProtocol]) {
for (i, element) in array.enumerated() {
let event = array[i]
if event.a != 0 {
preconditionFailure()
}
}
}
struct Event2: EventProtocol {
let a = 0
let b = 99
}
doWork(array)
doWork([Event2(), Event2()])
On my machine, both of those examples are just as fast as using Event
directly.
If you really need the doWork
function to support any
kind of EventProtocol
at any time – e.g. if you have 20 different possible event types and don't know which ones you'll have in your array, especially when there is a mix of types – then you'd need to use the original code, which has slower performance.
But you probably wouldn't do that with an Array containing 1 billion elements, like in the example. And there are other more performant ways to model that problem, for example with optional struct fields, with enum types, by splitting up the huge array into subarrays (or streams) containing only one type of element in each, etc. You'd also pay attention to using smaller Int
types (like UInt8
) to reduce memory usage, etc.
In short: you can get faster runtime performance by using the best tool for the job. Either just use a concrete type (Event
instead of EventProtocol
); avoid any
by using some
instead, as in some EventProtocol
; or model the data more efficiently for huge collections.
You're not going to have issues like this just writing some normal structs for a SwiftUI program – you really need to get into "big data" or have ultra low latency requirements for this to matter significantly.