Search code examples
iosswiftprotocol-oriented

Swift - Protocol extensions - Property default values


Let's say that I have the following protocol:

protocol Identifiable {
    var id: Int {get}
    var name: String {get}
}

And that I have the following structs:

struct A: Identifiable {
    var id: Int
    var name: String
}

struct B: Identifiable {
    var id: Int
    var name: String
}

As you can see, I had to 'conform' to the Identifiable protocol in struct A and struct B. But imagine if I had N more structs that needs to conform to this protocol... I don't want to 'copy/paste' the conformance (var id: Int, var name: String)

So I create a protocol extension:

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

With this extension now I can create a struct that conforms to the Identifiable protocol without having to implement both properties:

struct C: Identifiable {

}

Now the problem is that I can't set a value to the id property or the name property:

var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property

This happens because in the Identifiable protocol, id and name are only gettable. Now if I change the id and name properties to {get set} I get the following error:

Type 'C' does not conform to protocol 'Identifiable'

This error happens because I haven't implemented a setter in the protocol extension... So I change the protocol extension:

extension Identifiable {
    var id: Int {
        get {
            return 0
        }

        set {

        }
    }

    var name: String {
        get {
            return "default"
        }

        set {

        }
    }
}

Now the error goes away but if I set a new value to id or name, it gets the default value (getter). Of course, the setter is empty.

My question is: What piece of code do I have to put inside the setter? Because if I add self.id = newValue it crashes (recursive).

Thanks in advance.


Solution

  • It seems you want to add a stored property to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.

    I can show you a couple of alternatives.

    Subclassing (Object Oriented Programming)

    The easiest way (as probably you already imagine) is using classes instead of structs.

    class IdentifiableBase {
        var id = 0
        var name = "default"
    }
    
    class A: IdentifiableBase { }
    
    let a = A()
    a.name  = "test"
    print(a.name) // test
    

    Cons: In this case your A class needs to inherit from IdentifiableBase and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.

    Components (Protocol Oriented Programming)

    This technique is pretty popular in game development

    struct IdentifiableComponent {
        var id = 0
        var name = "default"
    }
    
    protocol HasIdentifiableComponent {
        var identifiableComponent: IdentifiableComponent { get set }
    }
    
    protocol Identifiable: HasIdentifiableComponent { }
    
    extension Identifiable {
        var id: Int {
            get { return identifiableComponent.id }
            set { identifiableComponent.id = newValue }
        }
        var name: String {
            get { return identifiableComponent.name }
            set { identifiableComponent.name = newValue }
        }
    }
    

    Now you can make your type conform to Identifiable simply writing

    struct A: Identifiable {
        var identifiableComponent = IdentifiableComponent()
    }
    

    Test

    var a = A()
    a.identifiableComponent.name = "test"
    print(a.identifiableComponent.name) // test