How to work ‘compile-speed friendly’ with structs in protocol-oriented programming?

I'm new to Swift and am working on SwiftUI development with iOS 16.0 as the minimum environment, and have read a few articles on protocol oriented programming over the year. I have learnt about the benefits it brings to project work.

I learnt the benefits it brings to project engineering. However the topic in the forum brings me to doubt, should the protocol be used more for SwiftUI related projects?

How to work ‘compile-speed friendly’ with structs in protocol-oriented programming? What are the implications if I want to introduce conformance and default implementations of other protocols for View structs?

Translated with DeepL Translate: The world's most accurate translator (free version)

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.

1 Like

This covers more than just existentials vs concrete types but I highly recommend watching this: Explore Swift performance - WWDC24 - Videos - Apple Developer.

Note: The issue you linked and the topics discussed in the video above relate to run-time performance. However, your question — "How to work ‘compile-speed friendly’ with structs in protocol-oriented programming?" (emphasis mine) — is about something else entirely. To answer that, I don't think there's a significant difference either way.

EDIT: Added note about compile-speed vs run-time performance.

2 Likes