75

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.

4 Answers 4

107

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
Sign up to request clarification or add additional context in comments.

8 Comments

@Axort: You're welcome. I also suggest you to watch this video about Protocol Oriented Programming from the 2015 WWDC. Very interesting.
Why do you have the HasIdentifiableComponent protocol? Why not just do protocol Identifiable: var identifiableComponent: IdentifiableComponent { get set } Then extension Identifiable { var id: Int { get { return identifiableComponent.id } set { identifiableComponent.id = newValue } } etc... }
Can you explain please why you create 2 protocols (HasIdentifiableComponent and Identifiable) in components example?
@BohdanSavych Simply because I wanted to separate the concept of HasIdentifiableComponent from the concept of Identifiable. If you don't like that separation feel free to group everything into one single protocol.
I'm getting a "Cannot assign to property: function call returns an immutable value" error in Swift 4, when trying to set either property. Similar error when trying to set those properties from within the conforming class, saying self is immutable. Do these need to be class-only protocols?
|
6

Objective-C Associated Objects

You can use Objective-C associated objects to basically add a stored property to a class or protocol. Note that associated objects only work for class objects.

import ObjectiveC.runtime

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

var IdentifiableIdKey   = "kIdentifiableIdKey"
var IdentifiableNameKey = "kIdentifiableNameKey"

extension Identifiable {
    var id: Int {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }

    var name: String {
        get { 
            return (objc_getAssociatedObject(self, &IdentifiableNameKey) as? String) ?? "default"
        }
        set {
            objc_setAssociatedObject(self, &IdentifiableNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }   
    }
}

Now you can make your class conform to Identifiable by simply writing

class A: Identifiable {

}

Test

var a = A()
print(a.id)    // 0
print(a.name)  // default
a.id = 5
a.name = "changed"
print(a.id)    // 5
print(a.name)  // changed

Comments

2

Protocols and protocol extensions are very powerful, but they tend to be most useful for read-only properties and functions.

for what you're trying to accomplish (stored properties with a default value), classes and inheritance might actually be the more elegant solution

something like:

class Identifiable {
    var id: Int = 0
    var name: String = "default"
}

class A:Identifiable {
}

class B:Identifiable {
}

let a = A()

print("\(a.id) \(a.name)")

a.id = 42
a.name = "foo"

print("\(a.id) \(a.name)")

1 Comment

Hi! Thanks for your answer. I was looking for a solution with Protocol-Oriented Programming instead of Object-Oriented Programming.
0

This is why you were not able to set the properties.

The property becomes a computed property which means it does not have a backing variable such as _x as it would in ObjC. In the solution code below you can see the xTimesTwo does not store anything, but simply computes the result from x.

See Official docs on computed properties.

The functionality you want might also be Property Observers.

Setters/getters are different than they were in Objective-C.

What you need is:

var x:Int

var xTimesTwo:Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

You can modify other properties within the setter/getters, which is what they are meant for

1 Comment

Hi! Thanks for your answer. Yes, as you said, the problem is that you can't declare vars in a extension (Extensions may not contain stored properties) so I was wondering if there was a way (a small chance) with computed properties :P

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.