iOS无痕埋点实现

本文最后更新于:2 年前

参考文章:iOS 无痕埋点解决方案—— AOP 篇(1)

一、AOP面向切面编程

AOP全称叫Aspect-Oriented Programming,中文名叫面向切面编程,在iOS平台中,AOP一般使用Runtime实现

二、代码实现

本文demo

UIKit提供的用户交互接口里,主要可以分为两种:

  • DelegateUITableView、UICollectionView的点击事件,特点是方法名定死,使用weak属性持有响应对象
  • Target-ActionUIControl、UIGestureRecognizer的回调事件,特点是方法名可自定义,方法参数可有可无,使用weak属性持有响应对象,支持多个响应者

1、Delegate类的交互

通常会拦截setDelegate:的方法,此时拦截到的delegate对象即为响应对象,再通过判断该响应对象是否实现了相应的代理方法,构建相同的方法进行交换,网上百度到的大部分都是这种实现的逻辑

但此方案存在如下问题:

  • 如果父类实现了setDelegate:和相应的代理方法,而具体的业务逻辑是由子类override重写的代理方法,那么判断到的埋点数据有可能存在问题
  • 如果同一个页面有多个UITableView,那么setDelegate:方法也会实现多次,此时需要防止出现多重交换的情况

参考文章中给出了其他方案:

  1. 拦截setDelegate:方法,重新创建一个proxy类绑定代理并实现相应的代理方法,并将该proxy类的对象交换给系统的delegate
  2. 使用Runtime将自定义的proxy类的对象添加到控件中持有
  3. 当触发代理方法时,系统实际持有delegate属性是proxy类的对应,会将触发方法发送到proxy类中,此时就可以进一步判断是否需要将代理方法继续转发到业务层
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import UIKit

/// 使用此类替代系统的delegate
class GHUITableViewDelegateProxy: NSObject, UITableViewDelegate {

weak var delegate: UITableViewDelegate?

override func responds(to aSelector: Selector!) -> Bool {
var hasSelector: Bool = false
if self.delegate == nil {
return hasSelector
}
hasSelector = self.delegate?.responds(to: aSelector) ?? true
return hasSelector
}

override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.delegate == nil {
return super.forwardingTarget(for: aSelector)
}
if self.delegate?.responds(to: aSelector) == false {
return super.forwardingTarget(for: aSelector)
}
return self.delegate
}

// MARK: UITableViewDelegate

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

if self.delegate == nil {
return
}
if (self.delegate?.responds(to: #selector(tableView(_:didSelectRowAt:)))) == false {
return
}

UITableViewTrack.shared.trackTableView(tableView, didSelectRowAt: indexPath)

self.delegate?.tableView?(tableView, didSelectRowAt: indexPath)
}
}

private func swizzle(_ tableView: UITableView.Type) {
let selectors: Array<Array<Selector>> = [
[
#selector(setter: tableView.delegate),
#selector(tableView.gh_setDelegate(_:))
],
]
for item in selectors {
let originalSelector: Selector = item[0]
let swizzledSelector: Selector = item[1]

let originalMethod: Method? = class_getInstanceMethod(tableView, originalSelector)
let swizzledMethod: Method? = class_getInstanceMethod(tableView, swizzledSelector)

if originalMethod == nil {
continue
}

let didAddMethod: Bool = class_addMethod(tableView, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if didAddMethod {
class_replaceMethod(tableView, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}

var ghUITableViewDelegateProxyKey = "GHUITableViewDelegateProxyKey"

extension UITableView {

var ghUITableViewDelegateProxy: GHUITableViewDelegateProxy? {
set {
objc_setAssociatedObject(self, &ghUITableViewDelegateProxyKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
get {
return objc_getAssociatedObject(self, &ghUITableViewDelegateProxyKey) as? GHUITableViewDelegateProxy
}
}

private static let dispatchOnceTime: Void = {
swizzle(UITableView.self)
}()

@objc open class func startAOP() {
guard self === UITableView.self else { return }
UITableView.dispatchOnceTime
}

// MARK: 交换的方法

// 交换setdelegate的方法
@objc func gh_setDelegate(_ delegate: UITableViewDelegate?) {

// 使用GHUITableViewDelegateProxy替代原来系统的UITableViewDelegate
self.ghUITableViewDelegateProxy = GHUITableViewDelegateProxy()
self.ghUITableViewDelegateProxy?.delegate = delegate

self.gh_setDelegate(self.ghUITableViewDelegateProxy)
}
}

2、Target-Action类的交互

image_1.png

按照参考文章中上图的逻辑设计代码如下:

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import UIKit

/// 自定义一个action类,由此类的实例对象替代外部传入的action
class GHUITapGestureRecognizerAction: NSObject {

// 弱应用,防止循环应用
weak var target: UIResponder?
var action: Selector?

@objc func ghGestureRecognizerAction(sender: UIGestureRecognizer?) -> Void {
if self.target == nil {
return
}
if self.action == nil {
return
}

UITapGestureRecognizerTrack.shared.trackGRAction(sender as? UITapGestureRecognizer, action: self.action!, target: self.target)

if ((target?.responds(to: action)) == true) {
target?.perform(action, with: sender)
}
}
}

var ghGestureActionsKey: String = "GHGestureActionsKey"

extension UIResponder {
/// 使用runtime给UIResponder扩展一个属性
var ghGestureActions: Array<GHUITapGestureRecognizerAction>? {
set {
objc_setAssociatedObject(self, &ghGestureActionsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
get {
return objc_getAssociatedObject(self, &ghGestureActionsKey) as? Array<GHUITapGestureRecognizerAction>
}
}
}

private func swizzle(_ gesture: UITapGestureRecognizer.Type) {
let selectors: Array<Array<Selector>> = [
[
#selector(UITapGestureRecognizer.init(target:action:)),
#selector(gesture.gh_init(target:action:))
],
[
#selector(gesture.addTarget(_:action:)),
#selector(gesture.gh_addTarget(_:action:))
],
]
for item in selectors {
let originalSelector: Selector = item[0]
let swizzledSelector: Selector = item[1]

let originalMethod: Method? = class_getInstanceMethod(gesture, originalSelector)
let swizzledMethod: Method? = class_getInstanceMethod(gesture, swizzledSelector)

if originalMethod == nil {
continue
}

let didAddMethod: Bool = class_addMethod(gesture, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if didAddMethod {
class_replaceMethod(gesture, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}

extension UITapGestureRecognizer {
private static let dispatchOnceTime: Void = {
swizzle(UITapGestureRecognizer.self)
}()

@objc open class func startAOP() {
guard self === UITapGestureRecognizer.self else { return }
UITapGestureRecognizer.dispatchOnceTime
}

// MARK: 交换的方法

@objc func gh_init(target: Any?, action: Selector?) -> UIGestureRecognizer {
if target == nil {
return self.gh_init(target: target, action: action);
}
if (target as? UIResponder) == nil {
return self.gh_init(target: target, action: action);
}

// 初始化判断
if (target as? UIResponder)?.ghGestureActions == nil {
(target as? UIResponder)?.ghGestureActions = []
}

// 一个手势对应一个GHUITapGestureRecognizerAction属性,再由target去强制有所有的GHUITapGestureRecognizerAction
let ghAction = GHUITapGestureRecognizerAction()
ghAction.target = (target as? UIResponder)
ghAction.action = action

(target as? UIResponder)?.ghGestureActions?.append(ghAction)

return self.gh_init(target: ghAction, action: #selector(ghAction.ghGestureRecognizerAction(sender:)))
}

@objc func gh_addTarget(_ target: Any, action: Selector) {
if (target as? UIResponder) == nil {
return self.gh_addTarget(target, action: action);
}

if (target as? UIResponder)?.ghGestureActions == nil {
(target as? UIResponder)?.ghGestureActions = []
}

let ghAction = GHUITapGestureRecognizerAction()
ghAction.target = (target as? UIResponder)
ghAction.action = action

(target as? UIResponder)?.ghGestureActions?.append(ghAction)

return self.gh_addTarget(ghAction, action: #selector(ghAction.ghGestureRecognizerAction(sender:)))
}
}

三、第三方库

很多第三方库也有功能强大的AOP逻辑,本文主要是轻量级的代码实现,其他第三方库暂不做介绍