Aaron Brager Aaron Brager - 1 year ago 107
Swift Question

Swift extension storage for protocols

I make a protocol:

protocol SomeProtocol {
func getData() -> String
}


I make a struct that conforms to it:

struct SomeStruct: SomeProtocol {
func getData() -> String {
return "Hello"
}
}


Now I want every
UIViewController
to have a property called
source
, so I can do something likeā€¦

class MyViewController : UIViewController {
override func viewDidLoad() {
self.title = source.getData()
}
}


To accomplish this, I create a protocol to define the property:

protocol SomeProtocolInjectable {
var source: SomeProtocol! { get set }
}


Now I just need to extend the view controller with this property:

extension UIViewController: SomeProtocolInjectable {
// ???
}


How can I hack together a stored property that will work with a protocol type?

What hasn't worked:


  • var source: SomeProtocol!
    obviously doesn't work because extensions don't have stored properties

  • I can't use Objective-C associated objects because a protocol isn't an object

  • I can't wrap it in a class (this does work for other value types, but not protocols)



Any other suggestions?

Answer Source

Any protocol object can be converted into a type-erased class. Build an AnySomeProtocol and store that.

private var sourceKey: UInt8 = 0

final class AnySomeProtocol: SomeProtocol {
    func getData() -> String { return _getData() }
    init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData }
    private let _getData: () -> String
}

extension UIViewController: SomeProtocolInjectable {
    var source: SomeProtocol! {
        get {
            return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol
        }
        set(newValue) {
            objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

class MyViewController : UIViewController {
    override func viewDidLoad() {
        self.title = source.getData()
    }
}

The caller can only use this to access the protocol methods. You can't force it back into its original type with as, but you should avoid that anyway.

As a side note, I'd really recommend making source return SomeProtocol? rather than SomeProtocol!. There's nothing here that promises that source will be set. You don't even set it until viewDidLoad.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download