adam tecle

Stored properties in a Swift extension

Sep 07, 2017

A neat workaround to get stored properties in extensions is to use associated objects. You can do it in Swift like this:

extension MyObject {
    var property: Any? { 
        get { 
            return objc_getAssociatedObject(self, &key)  
        }
        set(newValue) {
          objc_setAssociatedObject(self, &key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

A possible application of this in practice is to add some functionality for all instances of an object. Here’s how you can add a showLoadingIndicator() method to every UIView.

extension UIView {
  
  var loadingIndicator: LoadingIndicator? {
    get {
      guard let object = objc_getAssociatedObject(self, &key) as? LoadingIndicator else {
        return nil
      }

      return object
    }
    set(newValue) {
      objc_setAssociatedObject(self, &key, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
  }
  
  func showLoadingIndicator() {
    setupIfNeeded()
    loadingIndicator?.isHidden = false
  }

  func hideLoadingIndicator() {
    loadingIndicator?.isHidden = true
  }

  private func setupIfNeeded() { ... }
  
}

Using an associated object in this manner is probably not the first way I’d approach solving the problem of having some class get access to some shared functionality, like showing a loading indicator. If it’s the right tool for the job though, this approach works!

Here’s a convenient way to pretty up your syntax when getting/setting associated object by using a generic helper class, credit to Wojciech Nagrodzki

public final class Association<T: AnyObject> {

    private let policy: objc_AssociationPolicy

    /// - Parameter policy: An association policy that will be used when linking objects.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

        self.policy = policy
    }

    /// Accesses associated object.
    /// - Parameter index: An object whose associated object is to be accessed.
    public subscript(index: AnyObject) -> T? {

        get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

Now instead of those scary Objective-C runtime library methods, we have this nice subscript syntax.

extension SomeType {

    private static let association = Association<NSObject>()

    var simulatedProperty: NSObject? {

        get { return SomeType.association[self] }
        set { SomeType.association[self] = newValue }
    }
}