read

This is cool trick to inject a certain property for a type.

For example, if you want all UIControl to have a DisposeBag this is how you can inject.

extension UIControl {
  // 1. Declare a key which is needed to associate with the object
  private struct AssociatedKeys {
    static var DisposeBag = "DisposeBag"
  }

  var disposeBag: DisposeBag {
    get {
      // 2. Get it if exists
      if let disposeBag = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBag) as? DisposeBag {
        return disposeBag
      }

      // 3. Create if not
      let disposeBag = DisposeBag()
      objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, disposeBag, .OBJC_ASSOCIATION_RETAIN)
      return disposeBag
    }

    set {
      // 4. (Optional) Set it
      objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, newValue, .OBJC_ASSOCIATION_RETAIN)
    }
  }
}

DisposeBag is a RxSwift object, but it works for any objects that you want to inject.

The key methods used are objc_getAssociatedObject and objc_setAssociatedObject.

Should this be used?

This is a powerful tool, yet not as bad as method sizzling. I quote the dangers of sizzling:

Some people are scared of sharp knives because they think they’ll cut themselves badly, but the truth is that sharp knives are safer.

Method swizzling can be used to write better, more efficient, more maintainable code. It can also be abused and lead to horrible bugs.

You should form your own opinion once you fully understand both the good and the bad.

Do use sparingly.

Another example

I have seen this used in Coordinator, in one of their 4 classes.

// Inject parentCoordinator property into all UIViewControllers
extension UIViewController {
    private class WeakCoordinatingTrampoline: NSObject {
        weak var coordinating: Coordinating?
    }

    private struct AssociatedKeys {
        static var ParentCoordinator = "ParentCoordinator"
    }

    public weak var parentCoordinator: Coordinating? {
        get {
            let trampoline = objc_getAssociatedObject(self, &AssociatedKeys.ParentCoordinator) as? WeakCoordinatingTrampoline
            return trampoline?.coordinating
        }
        set {
            let trampoline = WeakCoordinatingTrampoline()
            trampoline.coordinating = newValue
            objc_setAssociatedObject(self, &AssociatedKeys.ParentCoordinator, trampoline, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

This is a trampoline pattern, where “execution” jumps with a certain memory location.

In our case, we simply inject a property at a certain memory location.


Image

@samwize

¯\_(ツ)_/¯

Back to Home