Optimizing Swift Code: Structure-Oriented Programming or Protocol-Oriented Programming?

cover
13 May 2024

In this article, we will look at the main aspects of structure-oriented programming: how to write code according to the SOLID principles without using protocols and without losing abstraction.

The Structure Oriented Programming paradigm is based on the fact that a structure can replace any protocol.

The advantage of this approach is performance. Since the structure-oriented approach is based on structures that use static dispatch, the dispatch speed will significantly differ from protocols with dynamic dispatch in the protocol-oriented approach.

1. Replacing a protocol with an equivalent structure

Let’s start by trying to replace the protocol with an equivalent structure.

In the protocol-oriented approach, the protocol itself allows abstraction from the implementation.

In the structure-oriented approach, generics and closures help to provide abstraction.

Consider, as an example, the composition of a protocol and a class, in which the protocol provides the square of a number for any object that will conform to this protocol:

// Abstraction
protocol RegularProtocol {
    var sqrValue: Int { get }
}

// Object
class RegularClass {
    
    let value: Int
    
    init(value: Int) {
        self.value = value
    }
}

// Realization
extension RegularClass: RegularProtocol {
    var sqrValue: Int {
        value * value
    }
}

let onbject = RegularClass(value: 25)
print(onbject.sqrValue)

Thus, the extension to the protocol adopt RegularProtocol.

For a structure-oriented approach, the above would look like this:

// Abstraction
struct RegularStruct<AdoptedObject> {
    var sqrValue: (AdoptedObject) -> Int
    
    init(sqrValue: @escaping (AdoptedObject) -> Int) {
        self.sqrValue = sqrValue
    }
}

// Object
class RegularClass {
    
    let value: Int
    
    init(value: Int) {
        self.value = value
    }
}

// Realization
extension RegularStruct where AdoptedObject == RegularClass {
    init() {
        sqrValue = { object in object.value * object.value }
    }
}

let onbject = RegularStruct<RegularClass>()
print(onbject.sqrValue(.init(value: 25)))

2. Covering all kinds of cases

To understand how to use the structure-oriented approach in practice, let’s consider the most likely cases of using the protocol:

  1. Computed property

  2. Property with getter and setter

  3. Static property

  4. Regular method

  5. Static function

  6. Function with an associated value

  7. Parent protocol function when inheriting protocols

For the protocol-oriented approach all these cases are presented below:

protocol ProtocolExample: ParentProtocolExpample {
    // 1
    var getVariable: Int { get }
    
    // 2
    var getSetVariable: Int { get set }
    
    // 3
    static var staticVariable: Int { get }
    
    // 4
    func regularFunction(value: Int) -> Bool
    
    // 5
    static func staticFunction(value: Int) -> Bool
    
    // 6
    associatedtype Value
    func assosiatedFunction(value: Value) -> Bool
}

protocol ParentProtocolExample {
    // 7
    func inheritedFunction() -> String
}

An equivalent composition for a structure-oriented approach looks like this:

struct StructExample<AdoptedObject, Value> {
    // 1
    var getVariable: (_ object: AdoptedObject) -> Int
    
    // 2
    var setVariable: (_ object: AdoptedObject, Int) -> Void
    
    // 3
    var staticVariable: () -> Int
    
    // 4
    var regularFunction: (_ object: AdoptedObject, _ value: Int) -> Bool
    
    // 5
    var staticFunction: (_ value: Int) -> Bool
    
    // 6
    var assosiatedFunction: (_ object: AdoptedObject, _ value: Value) -> Bool
    
    // 7
    var parentStruct: ParentStructExample<AdoptedObject>
    var inheritedFunction: () -> String
}

struct ParentStructExample<AdoptedObject> {
    var inheritedFunction: () -> String
}


3. Performance benefits

The effect of the implementation was tested with XCTest and the Optimization Level flags — Fastest, Smallest [-Os] and showed that dispatching is 3–4 times faster for the structure-oriented approach.

The Protocol-oriented approach:

The Structure-oriented approach:

Don’t hesitate to contact me on Twitter if you have any questions. Also, you can always buy me a coffee.