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:
- The
kvcstore
for the backing store. - Duplicate the
setValue()
function. - Duplicate the
getValue()
function.
Ok, that definitely sucks. There are two ways to get around this, namely:
- Throw away the protocol approach and use a base class with default implementations.
- 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
, andprivate
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.