November 12, 2015
Adding Custom Class Properties with Swift Extensions
The previous posting, Functionally Dreaming with Swift, was an exploration of what is possible when coding for iOS and OS X. As part of the code example I ran across the need to uniquely identify any given user interface element in Interface Builder (IB). One commonly explored way to do this is to use the restoration ID of a UIView as a unique identifier. But this has nothing to do with restoration id’s regular use. Using it for uniqueness but not restoration behavior doesn’t pass ‘the smell test.’ It abuses an existing property and can cause restoration behavior when it is not wanted.
What, then, is a poor Swift programmer to do? Swift extensions can’t add stored properties.
There is a solution. IB has the ability to apply Key-Value coding to any User Interface (UI) element but only for existing properties. Somehow stored properties have to be mocked-up so we can take advantage of IB’s Key-Value coding application. This is actually easier than one would think.
Let’s start by dropping IB from the picture so we can keep things simple. Imagine for some reason you wanted to extend all UIViews (this would include UIButtons, UILabels, UIImageViews, etc.) to have a stored property called sillyName. If all went well then you would be able to set the sillyName attribute in the ViewController’s viewDidLoad method.
Image 1 shows how this would look. Since sillyName would be like any other property it would be set in Swift’s normal way. You would also get the value by putting
let aSillyName = self.view.sillyName
in your code. The way to make this possible it to mock up a custom property.
In Swift, private declarations of structs is allowed in many places. Extensions is one of those places. Let’s draw on that and Swift’s ability to create calculated extension properties. Image 2 shows how to mock up a sillyName stored property as part of an extension.
Lines 27 – 29 embed a private struct into an extension. It is important to understand that this is different than attempting to add a named value (let) or a variable (var) to UIView as a part of the extension. In this case there is no instance of SillyCustomProperties created.
Instead of using an instance, we will use it ‘statically.’ This allows us to write code like line 32. In that line of code, the static struct property is accessed and returned from the get method of the extension’s calculated sillyName property. The sillyName calculated property is defined on line 30 as a String Optional and the setter is defined on lines 34 – 36.
This is a lot of fun, but there is a significant problem with the code in Image 2. The struct and its static values are shared among all UIView instances. That means that there could only be one sillyName and it would be applied to ALL UIViews. Each time it was set it would be updated for all UIViews. This means that we don’t yet have the ability to use this approach to apply unique identifiers to UIViews. If we tried they would all share the same ID. That is NOT good. To solve this problem we need to apply a little Objective-C ‘magic’.
Since UIView’s are instances of Classes and custom key-value pairs CAN be added to any class instance, we can ‘calculate’ a Swift property by storing and retrieving a value using Objective-C Key-Value coding. This can look a little nasty since we will need to call C functions and pass pointers.
Image 3 contains the ‘magic.’ Let’s replace the bodies of the get and set methods. In the new get method body, the objc_getAssociatedObject function retrieves the value associated with a custom key using Objective-C Key-Value coding. The function has two parameters. The first is the class instance that has the custom key and value. In this example it will be a UIView since we passed self and UIView is what we have extended.
The second parameter is the key for which we want the value. Line 40 shows the NSString pointer to the memory location of sillyName being passed. The & is what forces the parameter to be a pointer. NSString is inferred by the compiler since objc_getAssociatedObject requires an void*, C’s version of Swift’s AnyObject type, as its second parameter. If you would like more information on how to interact with C functions there is an earlier post about that.
The objc_setAssociatedObject is similar to objc_getAssociedObject but has one additional parameter, an indicator stating the memory for the NSString created as a result of calling the function should be retained rather than released. This reserves the memory used for the NSString until we decide to get rid of it (I.E. replace the value associated with sillyName with some other string). Now we can assign unique values to any type of UIView in interface builder.
Image 3 shows a UIImageView and the Identity Inspector’s (the blue icon in the Utilities list) description of the view. The section titled, “User Defined Runtime Attributes” is IB’s location where you can assign values to the properties of the UIView. In this case the value ‘squiggles’ is assigned to the sillyName property. Since sillyName was mocked up as a stored property of all UIViews by our extension, this definition does not cause a compile-time failure like it would if we hadn’t mocked up sillyName.
With all these pieces in place, we can modify this approach so we can meet the need for unique identifiers for any view. The code for this is almost exactly the same as what you have already seen. As seen in Image 5, the only differences are the name of the Struct and the name of the struct’s String property. Ignore the Bool properties. They are for something else entirely.
So there you are. This pattern can be used to add ‘stored’ properties to classes. I you want to see how the Unique ID was set for this code look in the Functionally Dreaming with Swift posting.