Issue #55

I’m trying to implement a tracker, so the idea is that it can inject subscription upon method calls. It is best suit for logging, analytics, and it leverages RxCocoa

Usage

track(ListController.self) { _ in
  print("")
}

track(ListController.self, selector: #selector(ListController.hello)) { _ in
  print("")
}

track(DetailController.self) { _ in
  print("")
}

The code

import UIKit
import RxSwift
import RxCocoa

var mapping: [String: [Item]] = [:]
var hasSwizzled = false
let bag = DisposeBag()

public func track<T: UIViewController>(_ type: T.Type, selector: Selector? = nil, block: @escaping (T) -> Void) {
  let typeName = NSStringFromClass(type)
  if !hasSwizzled {
    let original = #selector(UIViewController.viewDidLoad)
    let swizled = #selector(UIViewController.trackers_viewDidLoad)
    swizzle(kClass: UIViewController.self, originalSelector: original, swizzledSelector: swizled)
    hasSwizzled = true
  }

  let selector = selector ?? #selector(UIViewController.viewDidAppear(_:))

  let item = Item(selector: selector, block: { (controller) in
    if let controller = controller as? T {
      block(controller)
    }
  })

  if var items = mapping[typeName] {
    items.append(item)
    mapping[typeName] = items
  } else {
    mapping[typeName] = [item]
  }
}

class Item {
  let selector: Selector
  let block: (UIViewController) -> Void

  init(selector: Selector, block: @escaping (UIViewController) -> Void) {
    self.selector = selector
    self.block = block
  }
}

extension UIViewController {
  func trackers_viewDidLoad() {
    trackers_viewDidLoad()

    let typeName = NSStringFromClass(type(of: self))
    let items = mapping[typeName]
    items?.forEach({ (item) in
      self
        .rx
        .sentMessage(item.selector)
        .subscribe(onNext: { _ in
          item.block(self)
        }, onCompleted: {
          print("completed")
        })
        .disposed(by: bag)
    })
  }
}

func swizzle(kClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
  let originalMethod = class_getInstanceMethod(kClass, originalSelector)
  let swizzledMethod = class_getInstanceMethod(kClass, swizzledSelector)

  let didAddMethod = class_addMethod(kClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

  if didAddMethod {
    class_replaceMethod(kClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
  } else {
    method_exchangeImplementations(originalMethod, swizzledMethod)
  }
}