Swift extensions *can* add stored properties
Ok, the title is a complete lie: Swift extensions can only add computed properties. But you can got something just as good, if you’re willing to use Objective-C associated objects. There’s a lot of boilerplate, though, and if you do it the obvious way you sacrifice type-safety. Can we do better?
Yes we can. Here are a couple of little functions that, by the magic of Swift’s type-inference, let you write type-safe non-Optional computed properties that act just like stored properties, in a class or protocol extension.
import Foundationfunc associatedObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
initialiser: () -> ValueType)
-> ValueType {
if let associated = objc_getAssociatedObject(base, key)
as? ValueType { return associated }
let associated = initialiser()
objc_setAssociatedObject(base, key, associated,
.OBJC_ASSOCIATION_RETAIN)
return associated
}func associateObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
value: ValueType) {
objc_setAssociatedObject(base, key, value,
.OBJC_ASSOCIATION_RETAIN)
}
That’s all. You use them like so:
class Miller {} // Here's the class we will extendclass Cat { // Every Miller should have a Cat
var name = “Puss”
}private var catKey: UInt8 = 0 // We still need this boilerplate
extension Miller {
var cat: Cat { // cat is *effectively* a stored property
get {
return associatedObject(self, key: &catKey)
{ return Cat() } // Set the initial value of the var
}
set { associateObject(self, key: &catKey, value: newValue) }
}
}
If you’re following along at home, you can drop the following lines in a playground to confirm that it works as expected.
let grumpy = Miller()
grumpy.cat.name // shows "Puss"
grumpy.cat.name = “Hephaestos”
grumpy.cat.name // shows "Hephaestos"
Easy as that!
Credit where credit is due: at least half of this implementation comes from Eric-Paul Lecluse. He used associated objects in an extension, I extracted the pattern into a reusable class, then together we rendered the class down into the two little functions you see above. The writeup, and any errors it contains, is my own.