關(guān)于 UITableView 的 delegate 被 hook 時(shí)的問題

開發(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ú) UITableViewDelegate. 單獨(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è)子類 OneTableViewTwoTableView 分別繼承了 MyTableView, 在創(chuàng)建 OneTableViewTwoTableView 使用時(shí), 會(huì)在 tableView:didSelectRowAtIndexPath: 中陷入死循環(huán). 原因如下:

image.png

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)用.

解決方式, 下回分解.

喜歡和關(guān)注都是對(duì)我的鼓勵(lì)和支持~
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烁设,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子僵控,更是在濱河造成了極大的恐慌褒翰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異耻涛,居然都是意外死亡废酷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門抹缕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澈蟆,“玉大人,你說我怎么就攤上這事卓研∨糠” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵奏赘,是天一觀的道長(zhǎng)哮幢。 經(jīng)常有香客問我,道長(zhǎng)志珍,這世上最難降的妖魔是什么橙垢? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮伦糯,結(jié)果婚禮上柜某,老公的妹妹穿的比我還像新娘。我一直安慰自己敛纲,他們只是感情好喂击,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淤翔,像睡著了一般翰绊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旁壮,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天监嗜,我揣著相機(jī)與錄音,去河邊找鬼抡谐。 笑死裁奇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麦撵。 我是一名探鬼主播刽肠,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼免胃!你這毒婦竟也來了音五?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤羔沙,失蹤者是張志新(化名)和其女友劉穎躺涝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撬碟,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诞挨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呢蛤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惶傻。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖其障,靈堂內(nèi)的尸體忽然破棺而出银室,到底是詐尸還是另有隱情,我是刑警寧澤励翼,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布蜈敢,位于F島的核電站,受9級(jí)特大地震影響汽抚,放射性物質(zhì)發(fā)生泄漏抓狭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一造烁、第九天 我趴在偏房一處隱蔽的房頂上張望否过。 院中可真熱鬧,春花似錦惭蟋、人聲如沸苗桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煤伟。三九已至,卻和暖如春木缝,著一層夾襖步出監(jiān)牢的瞬間便锨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工我碟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸿秆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓怎囚,卻偏偏與公主長(zhǎng)得像卿叽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恳守,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345