Menu Home

Adding stored properties to an extension

Almost every iOS developer already faced this not-so-friendly message:

#blindPeople: the image shows an error message which says that extensions must not contain stored properties.

Yeah. Extensions cannot contain stored properties. If you need to add a property to a native component, you must create your own button inheriting from UIButton.

In my opinion, creating your own button using a UIButton inheritance is the best way to resolve this situation. But for all rule there is an exception and being able to place a property in an extension would save your life, if it was possible.

But is it really not possible?

There is a way to escape from this trap: the Objective-C Runtime.

Associated Objects

Associated Objects use that old concept that we learned in Object Oriented Programming classes. An object that has another object inside it.

With Objective-C Runtime, you may create objects and associate them in Runtime.

To archive that, we’re going to use two available functions in Objective-C Runtime: objc_getAssociatedObject and objc_setAssociatedObject.

Here is their definitions, by Apple docs:

objc_getAssociatedObject: Returns the value associated with a given object for a given key.

objc_setAssociatedObject: Sets an associated value for a given object using a given key and association policy.

Hey, wait a minute: association policy? What that means?

Association Policy

Association policy is about how the object will be associated with their owner. Do you remember the keywords strong, weak, copy, atomic, nonatomic? Yeah, that’s it.

There are 5 possible association types:

OBJC_ASSOCIATION_ASSIGN: Weak reference (weak)
OBJC_ASSOCIATION_COPY: Object copy (should implement NSCopying protocol) with atomic association.
OBJC_ASSOCIATION_COPY_NONATOMIC: Object copy (should implement NSCopying protocol) with non-atomic association.
OBJC_ASSOCIATION_RETAIN: Strong reference (strong) with atomic association.
OBJC_ASSOCIATION_RETAIN_NONATOMIC: Strong reference (strong) with non-atomic association.

Let’s see an exemple that shows everything we talked so far.

Implementing an action handler in a UIButton

First, let’s create a UIButton extension and place our action handler.

#blindPeople: the image shows a code where UIButton is extended with an action handler is created with no success, with an error message in that line, saying that extensions must not contain stored properties.

Yeah, it didn’t work…

Let’s use objc_getAssociatedObject and objc_setAssociatedObject so we can turn our stored property into a computed property, allowed in extensions.

We just need to create a get and set in our property, as shown in next example:

fileprivate struct UIButtonAssociatedKeys {
    static var action: UInt8 = 0
}

extension UIButton {

    var actionHandler: ((UIControl) -> Void)? {
        get {
            return objc_getAssociatedObject(self, &UIButtonAssociatedKeys.action) as? ((UIControl) -> Void)
        }
        set {
            objc_setAssociatedObject(self, &UIButtonAssociatedKeys.action, newValue, .OBJC_ASSOCIATION_COPY)
        }
    }

}

In get section, we will search inside self (our UIButton) for an associated property defined by the reference key UIButtonAssociatedKeys.action, and return it as our action.

In set section, we will store in self (nosso UIButton) an associated object (newValue) that will be referenced by the key UIButtonAssociatedKeys.action, storing a copy of this object.

Thus, we will store and redeem our action handler in any UIButton.

Making an action hander responds to a button tap

In the same way that we would use an addTarget to implement the button tap via code, we will create a tap function to store our action handler and set it for TouchUpInside.

That way, our final code would look like this:

import Foundation
import UIKit

fileprivate struct UIButtonAssociatedKeys {
    static var action: UInt8 = 0
}

extension UIButton {

    private var actionHandler: ((UIControl) -> Void)? {
        get {
            return objc_getAssociatedObject(self, &UIButtonAssociatedKeys.action) as? ((UIControl) -> Void)
        }
        set {
            objc_setAssociatedObject(self, &UIButtonAssociatedKeys.action, newValue, .OBJC_ASSOCIATION_COPY)
        }
    }

    @objc private func executeAction(_ sender: UIControl) {
        self.actionHandler?(sender)
    }

    func tap(_ action: @escaping (UIControl) -> Void) {
        self.actionHandler = action
        self.addTarget(self, action: #selector(executeAction(_:)), for: .touchUpInside)
    }
}

That’s all folks

Guys, I hope you enjoyed this tip! Soon there will be more tips, more tutorials and much more content for iOS devs.

Categories: Tutorials

Tagged as:

André Salla