開發(fā)中必不可少的一個(gè)環(huán)節(jié)就是產(chǎn)品經(jīng)理要求添加的埋點(diǎn), 從是否需要開發(fā)者添加代碼的角度, 分為無侵入埋點(diǎn)
和事件埋點(diǎn)
. 其中 無侵入埋點(diǎn)
SDK 的實(shí)現(xiàn)原理就是在 hook 了所有UI 控件的事件.
頁面展示的 UI 控件, 最常見的就是 UITableView, 那么如何 hook 它的點(diǎn)擊事件呢?
UITableView 的點(diǎn)擊事件 tableView:didSelectRowAtIndexPath:
由其 delegate
實(shí)現(xiàn). 最直接的想法, 就是在設(shè)置 UITableView 的 delegate 時(shí), 交換 delegate 中實(shí)現(xiàn)的方法:
@implementation UITableView (switchDelegate)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self methodSwizzlingWithOriginalSelector:@selector(setDelegate:) bySwizzledSelector:@selector(ZD_setDelegate:)];
});
}
-(void)ZD_setDelegate:(id<UITableViewDelegate>)delegate
{
SEL sel = @selector(tableView:didSelectRowAtIndexPath:);
SEL sel_t = @selector(ZD_tableView:didSelectRowAtIndexPath:);
//如果 delegate 沒實(shí)現(xiàn)tableView:didSelectRowAtIndexPath:就不需要hook
if (![delegate respondsToSelector:sel]){
return;
}
BOOL addsuccess = class_addMethod([delegate class],
sel_t,
method_getImplementation(class_getInstanceMethod([self class], sel_t)),
nil);
//如果添加成功了就直接交換實(shí)現(xiàn), 如果沒有添加成功侦厚,說明之前已經(jīng)添加過并交換過實(shí)現(xiàn)了
if (addsuccess) {
Method selMethod = class_getInstanceMethod([delegate class], sel);
Method sel_Method = class_getInstanceMethod([delegate class], sel_t);
method_exchangeImplementations(selMethod, sel_Method);
}
[self ZD_setDelegate:delegate];
}
- (void)ZD_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __func__);
[self ZD_tableView:tableView didSelectRowAtIndexPath:indexPath];
}
//NSObject 分類 Swizzling 中封裝了方法交換方式
@implementation NSObject (Swizzling)
+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
+ (void)methodSwizzlingWithClassSelector:(SEL)originalClassSelector
byClassSelector:(SEL)swizzledClassSelector{
Class class = object_getClass([self class]);
Method originalMethod = class_getInstanceMethod(class, originalClassSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledClassSelector);
BOOL didAddMethod = class_addMethod(class,originalClassSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,swizzledClassSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
使用以上方式, 能實(shí)現(xiàn) hook 單獨(dú) UITableView
的 Delegate
. 單獨(dú)的意思是 該UITableView 沒有子類
. 比如新建 MyTableView
, 在其初始化方法中, 設(shè)置其本身為 delegate
:
@interface MyTableView ()<UITableViewDataSource, UITableViewDelegate>
@end
@implementation MyTableView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
self = [super initWithFrame:frame style:style];
if (self) {
self.dataSource = self;
self.delegate = self;
}
return self;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath");
}
@end
如果有兩個(gè)子類 OneTableView
和 TwoTableView
分別繼承了 MyTableView
, 在創(chuàng)建 OneTableView
和 TwoTableView
使用時(shí), 會(huì)在 tableView:didSelectRowAtIndexPath:
中陷入死循環(huán). 原因如下:
在 OneTableView
設(shè)置 delegate
后, 其父類 MyTableView
中的 tableView:didSelectRowAtIndexPath:
方法實(shí)現(xiàn), 已經(jīng)指向了 UITableView+switchDelegate
中的 ZD_tableView:didSelectRowAtIndexPath:
. 當(dāng)在 TwoTableView
中再次設(shè)置 delegate
時(shí), 將父類 MyTableView
中的 ZD_tableView:didSelectRowAtIndexPath:
再次指向 UITableView+switchDelegate
中的函數(shù)本身, 在調(diào)用時(shí)就發(fā)生了循環(huán)調(diào)用.
解決方式, 下回分解.