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.

Axort
  • 2,034
  • 2
  • 23
  • 24

4 Answers4

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
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Hi! Thanks for your answer. I really like that component technique. I'm trying to adopt the Protocol Oriented Programming from now on so I was looking for something like that. – Axort Aug 11 '16 at 15:34
  • 1
    @Axort: You're welcome. I also suggest you to watch this [video](https://developer.apple.com/videos/play/wwdc2015/408/) about _Protocol Oriented Programming_ from the 2015 WWDC. Very interesting. – Luca Angeletti Aug 11 '16 at 15:35
  • 10
    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... }` – Above The Gods Feb 02 '17 at 04:19
  • 1
    Can you explain please why you create 2 protocols (HasIdentifiableComponent and Identifiable) in components example? – Bohdan Savych May 24 '17 at 10:40
  • 4
    @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. – Luca Angeletti May 24 '17 at 16:31
  • 1
    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? – ekreloff Jan 16 '18 at 19:25
  • If you're happy to involve the Objective C runtime you can also take advantage of Associated Objects as your backing store for the extension properties. See http://nshipster.com/swift-objc-runtime/ and https://marcosantadev.com/stored-properties-swift-extensions/ for examples and discussion. – Robin Macharg Mar 12 '18 at 12:36
  • @LucaAngeletti how would you handle this when a protocol defines an associatedtype and also has a stored property which is of this type. – cohen72 Dec 09 '18 at 07:53
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

DominicMDev
  • 89
  • 1
  • 1
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)")
Nick
  • 2,361
  • 16
  • 27
  • 2
    Hi! Thanks for your answer. I was looking for a solution with Protocol-Oriented Programming instead of Object-Oriented Programming. – Axort Aug 11 '16 at 15:35
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

Harsh
  • 2,852
  • 1
  • 13
  • 27
  • 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 – Axort Aug 11 '16 at 15:40