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 Foundation
func 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 extend
class 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.

Like what you read? Give Tikitu de Jager a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.