How to implement a tracker in Swift

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

1
2
3
4
5
6
7
8
9
10
11
track(ListController.self) { _ in
print("")
}

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

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

The code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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)
}
}

Comments