Protocols and Hidden Details

Protocols (and their extensions)… those glorious things that they are. Well, mostly. I'm still finding a few places where they are coming up a bit short, or at least are not providing a good long-term solution.

Let's say you want to create a protocol, like I did with the Key Value Coding post.

protocol KeyValueCodable {
    mutating func setValue<T>(value: T, forKey: String) throws
    func getValue<T>(forKey: String) throws -> T
}

What this says is we want a protocol that defines a setValue() and a getValue() function. No problems here.

Now, it's time to implement this protocol.

struct Person : KeyValueCodable {
    private var kvcstore: [String:Any] = []

    mutating func setValue<T>(value: T, forKey: String) throws {
        // a bunch of logic to validate the correct type for the key and value; throw on error.
    }

    func getValue<T>(forKey: String) throws -> T {
        // a bunch of logic to validate the correct type for the key and value; throw on error.
    }

    var name: String {
        get { return getValue("name") as String }
        set { setValue(newValue, forKey: "fname") }
    }
}

Well, what happens when we want to add a new type Address that also conforms to the KeyValueCodable protocol? Well, we are going to need to implement:

  1. The kvcstore for the backing store.
  2. Duplicate the setValue() function.
  3. Duplicate the getValue() function.

Ok, that definitely sucks. There are two ways to get around this, namely:

  1. Throw away the protocol approach and use a base class with default implementations.
  2. Use protocol extensions with default implementations.

Unfortunately, both have significant drawbacks. The class version forces you into reference semantics for all KeyValueCodable types, which is less than desirable. With protocol extensions, we are forced to make our internal implementation publicly exposed (well, exposed at the same access level of the protocol).

The basic problem is this: there is no way to create a protocol that is for consumers of the API and a protocol for implementors of an API. rdar://21850541

This is one of the fundamental arguments about Swift's public, internal, and private access modifiers. They do not allow for this type of design pattern, and it's necessary.

So, in this strongly typed langauge of ours, we solve this problem by convention!

protocol KeyValueCodable : _KeyValueCodable {
    mutating func setValue<T>(value: T, forKey: String) throws
    func getValue<T>(forKey: String) throws -> T
}

protocol _KeyValueCodable {
    static var _codables: [KVCTypeInfo] { get }
    var _kvcstore: Dictionary<String, Any> { get set }
}

extension KeyValueCodable {
    mutating func setValue<T>(value: T, forKey: String) {
        // default implementation goes here...
    }

    func getValue<T>(forKey: String) -> T {
        // default implementation goes here...
    }
}

That's right, create another protocol prefixed with _ and everyone knows you are up to some dirty little secret tricks that you unfortunately need to expose to everyone so they can violate all of your assumptions. Good times.

This leads me to my next problem: these default implementations are going to need to work on some data; it would be really nice if we could also create a default store in our extension so that all of the implementors do not have to do this each time to get the default behavior (rdar://21844730):

struct Person : KeyValueCodable {
    var _kvcstore: Dictionary<String, Any> = [:]
}

Just copy/paste those member fields (sure, it's just one in this example) for each type that extends KeyValueCodable. I sure hope the protocol doesn't change how it needs to store that backing information, otherwise you're out of luck.

Protocols are pretty great and made even better with extensions. However, there is still more required from to reach their potential.

Protocols and Hidden Details