Appending arbitrary properties to objects in Swift

Adolfo Rodriguez
relevant stories
Published in
2 min readDec 17, 2015

Although some may regard it as a lazy solution to a problem that may be more cleanly tackled with a subclass, there are some extreme cases in which you may wish to append an arbitrarily named stored property to an existing object.

This is how you would do this JavaScript:

someRandomObject.myNewPropertyName = someStuffIWillUseLater;

(JavaScript developers do not need blog posts for stuff like this).

Existing Solutions

The key to many partial solutions to this general problem is to use Objective-C’s “objc_setAssociatedObject” function. This function is particularly useful for extending classes with new stored properties, as follows:

var key:UInt8 = 0let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMICextension UIView {
var name:String? {
get {
return objc_getAssociatedObject(self,&key) as? String
}
set {
objc_setAssociatedObject(self,&key,newValue,policy)
}
}
}

The issue with this solution is that the property “name” is not arbitrary at all. We had to know before compilation that we needed such property with that particular type, on all UIView objects, and we had to define the global var “key” to serve as a handle for this property.

Additionally, the type of the property needs to conform to the protocol AnyObject (String does, because Swift).

My Solution

This Stack Overflow answer uses Swift’s generics to enable appending properties of any type. This idea is the basis for my solution, but I go a bit further by getting rid of the need for the ugly global handler variable “key,” and as a result, allowing you to append arbitrarily named properties at runtime. This is what it looks like to make use of this solution:

//Setting properties:
view-->["name":"My View"]
view-->["importantArea":CGRectMake(10,10,20,20)]
//Retrieving properties
let name:String? = view-->"name"
let importantArea:CGRect? = view-->"importantArea"
//Removing a property
view-->["name":nil as String?]
//Extending classes
extension UIView {
var name:String? {
get {return self-->"name"}
set {self-->["name":newValue]}
}
}

My original code involved a dictionary of wrapped variables which would serve as global handlers. However, Reddit user Kametrixom has suggested a simplified version for the associativeKey String extension which I have implemented.

The full implementation follows. Please feel free to write your feedback below :)

The Code

import Foundationpublic final class Box<T> {
public init(_ value: T) {
self.value = value
}

public private(set) var value: T
}
private extension String {
var associativeKey:UnsafePointer<Void> {
return unsafeBitCast(hashValue, UnsafePointer<Void>.self)
}
}
infix operator --> { precedence 180 }public func --> <T>(object:AnyObject,key:String) -> T? {
if let v = objc_getAssociatedObject(object, key.associativeKey)
as? Box<T> {
return v.value
} else {
return nil
}
}
public func --> <T>(object:AnyObject,dict:[String:T?]) {
for (key,value) in dict {
if value == nil {
objc_setAssociatedObject(object, key.associativeKey,
nil,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(object, key.associativeKey,
Box(value!),
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

Follow me for more snippets of our code. Download the Relevant iOS App to see it all in action :)

Originally posted for relevant.ai.

--

--