一、前语

在上篇《七步结束列表点击作业的收集》文章中咱们现已具体介绍了如安在运行时创立子类进行 cell 点击作业收集,本篇将继续谈论在真实场景中所遇到的问题,并逐一进行处理。

二、踩过的坑

2.1KVO
当咱们对一个开源矿工政策进行 KVO 特征监听时,体系也github中文官网网页会为该政策新建一个 NSKVONotifying_ 最初的暂时类,关于 KVO 的结束可参阅苹果官网文档[1开源是什么意思]。

当咱们和体系都为署理giti政策的类新建子类时,情况就会变得非常复杂。

2.1.1. 场景一

先设置署理政策,然后对署理政策进行 KVO 特征监听,如图 2-1 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-1 场景一的 isa 指针改动进程图

这种GitHub场景下会存在gitlab下述问题:

体系在新建 NSKVONotifying_Degithub中文社区legate 类时,也会apple官网重写 – class 办法,用于躲藏这个暂时apple watch类。在这个场景中 NSKVONotifying_Delegate 继承自 Sensapple id密码重置orsDelegate,因而 – class 办法的回来值为咱们新创立的子类信息,并不是原始类信息。

处理方github永久回家地址案:

咱们能够在新建子类后,对 – addObserver:forKeyPath:options:context: 办法进行监听。假定署理政策在咱们新建子类后又进行了 Kgiti轮胎是什么品牌VO 特征监听,咱们就需求在体系重写 – class 办法后,再次进行重写,并回apple官网来原始类:

[SAMethswift结算体系odHelper addInstanceMethodWithSelector:@selector(addObserver:forKeyPath:options:context:) fromClass:proxyClass toClass:realClass];

  • (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    [supgithub永久回家地址er addObserver:observer forKeyPath:keyPath options:options contgiti轮胎ext:context];
    if (self.sensorsdata_className) {
    // 因为添加了 KVO 特征监听, KVO 会创立子类并重写 CGitlass 办法,回来原始类; 此刻的原始类为神策添加的子开源我国类,因而需求重写apple id密码重置 class 办法
    [SAMethodHelper replaceInstanceMethodWswiftlyithDgithub下载estinationSelector:@selector(clagithub中文官网网页ss) sourceSelector:@selector(class) fromClass开源节流:SAswift代码DelegateProxy.class toClass:[SAClassgiti轮胎是什么品牌Helper realClassWithObject:self]];
    }

}

2.1.2. 场景二

先设置署理政策,然后进行 KVO 特征监听,究竟移除 KVO 特征监听,如图 2-2 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-2 场景二的 isa 指针改动进程图

这种场景下没有问题。

2.1.3. 场景三

先对署理政策进行 KVO 特征监听,再进行署理政策的设置,如图 2-3 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-3 场github官网景三的 isa 指针改动进程图
这种场景下会存在下述问题:

在该场景中 SensorsDelegate 继承自 NSKVONotifying_Delegate,这会对体系的 KVO 特性有所影响,在进行特征赋值时会引发溃散。

处理计划:

假定署理政策的 isa 指针指向的是一个 NSKVONotifying_ 的类,那咱们便不再新建子类,而是直接重写 NSKVONotifying_ 类中的 – tableView:didSelectRowAtIngithub中文社区dexPath: 办法:

if ([SADel开源矿工egateProxy isKVOClass:realClass]) {
[SAMethodHelper addInstanceMswift体系ethodWithSelector:ta开源阅览app下载安装blViewSelector fromClass:开源矿工proxyClass toClass:realClass];
[SAMethodHelper addIswift怎样读nstanceMethodWithSelector:collectionViewSelector fromClass:proxyClass toClass:re开源是什么意思alClass]github中文官网网页;
return;
}

2.1giti.4. 场景四

先对署理政策进行 KVO 特征监听,再进行github永久回家地址署理政策的设置,究竟移除 KVO 特征监听,如图 2-4 所示:

又翻车了?列表点击事情收集那些你不知道的坑!
图 2-4 场景四的 isa 指针改动进程图
这种场景下会存在下述问题:

在移除 KVO 时,体系会将署理政策的github中文官网网页 isa 指针直接指apple tv回原始类,这时便无法进行点击作业收集了。

处理计划:

在 NSKVONotifying_ 的类中重写 – tableView:didgithubSelectRowAtIndexPath: 办法的一同,对 – removeObserver:开源阅览forKeyP开源节流ath: 办法进行监听,在移除 KVO 特征监听时对github署理政策再次实施新建子类的操作:

if ([SADelegaswift什么意思teProxy isKVOClass:realClass]) {
[SAMethodHelper aswift体系ddInstanceMethodWit开源hSelector:@selector(removeObse开源矿工rver:forKeyPath:) fromClass:pr开源阅览app下载安装oxyClass toClass:github怎样下载文件realClass];
return;
}

  • (void)removeObserver:(NSObject *)apple watchobserver forKeyPath:(NSString *)keyPath {
    // rGitemoapple官网ve 前署理政策是否归属于 KVO 创立的类
    BOOL oldClassIsKVO = [SADelegateProxy isKVOClass:[Sgithub永久回家地址AClassHelper realClassWithObject:self]];
    [super removeObserver:observer forKeyPath:keyPath];
    // remove 后署理政策是否归属于 KVO 创立的类
    BOOL newClassIsKVO = [SADelegateProxy isKVOClass:[SAClassHelper realClassWithObgithub是干什么的ject:self]];

    // 有多个特征监听时, 在究竟一开源阅览个监听被移除后, 政策的 iswift体系sa 产生改动, 需求从头为署理政策添加子类
    if (oldClassIsKVO && !newClassIsKVO) {
    // 清空现github直播渠道永久回家已记载的原始类
    self.sensorsdata_className = nil;
    [SADelegateProxy proxyWithDelegate:self];
    }

}

2swift结算体系.1.5. 究竟流程

开源软件竟处理流程如图 2-5 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-5 处理流程图
2.2RxSwift
在七步结束列表点击作业的收集文章中现已说到关于 cell 点击音讯的处理逻辑,对 Rswift怎样读xSwif开源阅览t 场景下进行了音讯转发,此刻疏忽了giti轮胎一个重要害:

假定运用体系办法设置了 UITableView 的 delegate,这时 RxSwift 会在内部运用 _forwardToDelapple官网egate 持有该 delegate,然后在音讯转发阶段开源我国,对该署理政策发送一次消github永久回家地址息,用于确保业务逻辑正常触发。swift什么意思

可是此刻咱们现已为 delegate 创立了子类,重写了 – tableView:didSelectRowAtIndexPswift言语ath: 办法。因而在 RxSwift 对署理政策发送的音讯会被咱们接纳,究竟导致办法递归调用引swift怎样读发溃散。

音讯发送如图 2-6 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-6 音讯发送进程app store

参阅 _RXD开源elegateProxy 的源码[2],- forwardInvocation: 的giti轮胎结束如下所示:

  • (void)forwardInvocation:(NSInvocation *)anInvocation {
    BOOLGit isVAppleoid = RX_is_method_signature_void(anInvocation.methodSignature);
    NSArray *arguments = nil;
    if (igiti轮胎sVoid) {
    arg开源软件uments = RX_extract_arguments(anInvocation);
    [self _sentMessage:anInvocationapple watch.selector withArguments:arguments];
    }

    if (self._forwardToDelegate && [self._forwardToDelegate respondsToSegithub中文官网网页lector:anInvoca开源tion.selector]) {
    [anInvocation invokeWithTarget:self._fogiteerwardToDelegateswift结算体系];
    }

    if (isVoid) {
    [self _methodInvoked:anInvocation.selector withArgumgithub永久回家地址enswiftkeyts:arguments];
    }

}

已然 RxSwift 内部会在音讯转发时调用 _forwardToDelegate 的 IMP,那么咱们在检测到 _forwardToDelegate 时直接调用 IMP,而不是再次进行音讯转发即可处理该问题,结束逻辑如下:

  • (void)tableView:(UITableView *)tabapple tvleView didSelectRow开源矿工AtIndexPath:(NSIndexPath *)indexPath {github中文社区
    SEL methodSelector = @selector(tableView:didSelectRowAtIndexPath:);
    [SADelegaswift代码teProxy invokeWithScrollView:tabgithub敞开私库leView selector:methodSelector selectedAtIndexPath:iswift言语ndexPath];

}

  • (void)invokeWitgithub敞开私库hScrollView:(UIScrollView *)scrolswifterlView selector:(SEL)selector selectedAtIndexPath:(开源软件NSIndexPath *)indexPath {
    NSObject *delegate = (NSObject *)scrollView.delegate;
    Class originalClass = NSClassFromSapp storetring(delegate.sensorsdata_className) ?: delegate.class;
    IMP originalIMP = [SAMethodHelper implementationOfMswiftkeyethodSelector:selector fromClass:github敞开私库originalClapp storeass];
    if开源节流 (originalIMP) {
    ((SensorsDidSelectImplementatioswiftkeyn)originalIMP)(delegate, selector, scrollView, indexPgithub永久回家地址ath);
    } else if ([SAgithubDelegateProxy isRxDelegateProxyClass:originalClass]) {
    NSObject *forwardToDelegate = nil;
    if ([delegate respondsToSelecgit指令tor:NSSelectoswift什么意思rFro开源mString(@”_forgithub中文官网网页wardToDelegate”)]) {
    // 获取 _forwardToDelegate 特征
    forwardToDelegate = [delegate valueForKey:@”_forwardToDelegate”];
    }
    if开源阅览 (forwardToDelegate) {
    Cgithub永久回家地址lass forwardOriginagiti轮胎是什么品牌lClass = NSClassFromString(forwardToDelegate.sensorsdata_className) ?: forwardToDelegate.class;
    IMP forwardOriginalIMP = [SAMethodHelperapp store implementationOfMethodSelector:selector fromClass:fswiftkeyorwardOriginalClass];
    if (forwardOriginalIMP) {
    ((SensorsDidSelectImplementation)forwardOriginalIMP)(forwardToDelegate, selector, scrollView, ingit指令dexgiti是什么牌子Path);
    }
    } else {
    ((SensorsDidSelecswift代码是什么意思tImplementation)_objc_msgForward)(delegate, selector, scrollView, inde开源众包xPath);
    }
    }
    // 作业收集
    // …

}

可是这种处理办法又存在其他一个问题:一同运用体系办法设置署理和运用订阅的办法订阅git指令点击回调,那么订阅的办法将会无效,因为咱们没有再次进行音讯转发。

修改后的音讯发送如图 2-7 所示:

又翻车了?列表点击事情收集那些你不知道的坑!

图 2-7 修改后的音讯发送进程
为了彻gitlabgithub永久回家地址兼容 RxSwift,咱们需求把 _RXDelegateProxy 的 – forwardInvocation: 逻辑结束一遍,直接调用其内部的办法,具体结束如下:

  • (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    SEL methodSelector = @selector(tableView:didSelectRowAtIndexPath:);
    [SADelSwiftegateProxy invokeWithScrollView:tableView selector:methogithub永久回家地址dSelector selectedAtIndexPath:indexPath];

}

  • (void)invokeRXProxyMethodWithTarget:(id)target selector:(SEL)selegiti是什么牌子ctor argument1:(SEswift体系L)arg1 argument2:(giti是什么牌子id)arg2 {
    Class cla = NSClassFromString([target sensorsdata_className]) ?: [swift结算体系target cgithub敞开私库lass];
    IMP implementation = [SAMethgithub敞开私库odHelper implementationOfMethodSelector:selector fromClass:cl开源矿工a];
    if (implementation) {
    void(imp)(id, SEL, SEL, id) = (void()(id, SEL, SEL, id))impapp storelementation;
    imp(target开源我国, selector, arg1, agithub官网rggithub永久回家地址2);
    }

}

/// 实施 RxCocoa 中,点击作业相关的响应办法
/// 这个办法中调用的次第和 _RXDSwiftelegateProxy 中的 – forwardInvocation: 办法实施相同
/// @param scrolapple idlVieswift什么意思w UITableV开源软件iew 或许 UICollectionVie开源节流w 的政策
/// @param selector 需求实施的办法:tableView:didgithub官网SelectRowAtIndexPgithub直播渠道永久回家ath: 或许 collecgithub永久回家地址tionView:didSelectItemAtIndexPath:github是干什么的
/// @param indexPath 点击的 NSIndexPath 政策

  • (void)rxInvokeWithScrollView:(UIScrollView *)scrollView selectAppleor:(SEL)selector selectedAtIGitHubndexPathswift什么意思:(NSIndexPath *)indexPath {
    // 1. 实施 _sentMeswiftkeyssage:withArguments: 办法
    [SADapple id密码重置eleggithub官网ateProxy invokeRXProxyMethodWithTarget:s开源软件crollView.delegate selector:NSSelectorFromString(@”_sentMessage:withArguSwiftments:”) argument1:selector argument2:@[scrollView, indexPatswiftlyh]];

    // 2. 实施 UIKit 的署理办法
    NSObjectgithub永久回家地址 *forwardToDelega开源代码网站githubte = nil;
    SEL fgithub是干什么的orwardDelegateSelector = NSSelectorFromString(@”_forwardToDelegate”);
    IMP forwardDelegateIMgithub中文官网网页P = [(NSObGitject *)scrollView.giti是什么牌子delegate methodForSelector:forwardDelegateSelector];
    if (forwardDelegateIMP) {
    forwardToDelegate = ((NSObject ()(id, SEL))forwardDelegateIMP)(scrollView.delegate, forwardDelegateSelector);
    }
    if (forwardToDeleggithub中文官网网页ateswift言语) {
    Class forwardOriginalClass = NSClassFromString(forwardToDelegate.sensorsdata_className) ?: forwardToDelegate.class;开源软件
    IMP forwardOriginalIMP = [SAMethodHelper implementationOfMethodSelector:selector fromClass:forwardOrGitiginalClass];
    ifgithub怎样下载文件 (forwardOriginalIMP) {
    ((SensorsDigithub官网dSelectImplementatigithub是干什么的oapple payn)forwardOriginalIMP)(forwardToDelegate, selector, scrollView, indexPath);
    }
    }

    // 3. 实施 _methodInvoked:withArguments:swiftly 办法swiftkey
    [SADelegateProxy invokeRXProxyMethodWithTarget:scrollVieswift体系w.delegate selector:NSSelectorFromString(@”_methodInvoked:withArguments:”) argument1:selector argumeApplent2:@[scrollView, indexPath]];

}

  • (voiapple id密码重置d)in开源众包vokeWithScrollView:(UIScrollView *)scrollView selector:github中文社区(SEL)selector selectedAtIndexPath:(NSIndexPath *)indexPath {
    NS开源节流Object *delegate = (NSObject *)scrollView.delegate;
    // 优先获取记载的原始父类, 若获取不到则是 KVO 场景, KVO 场景经过 class 接口获取原始类
    Class originalClass = NSClasswift什么意思sFromString(delegate.sensorsdata_className) ?: delegate.class;
    IMP originalIMP = [SAMethodHelper implementationOfMethodSelector:selector fromClass:originalClass];
    if (originalIMP) {
    ((SensorsDidSapple payelectImplementationapple id密码重置)originalIMP)(delegate, selector, scrollView, indexPath);
    } else if ([SADelegateProxy isRxDelegateProxyClass:ogithub怎样下载文件riginaswift结算体系lClass]) {
    [SADelegateProxy rxInvokeWithScrollView:scrollView selector:selector selectedAtIndexPath:SwiftindexPath];
    }
    // 作业收集
    // …

}

音讯发送
上一节中尽管对 RxSwift 进行了适配,可是存在许多未知的三方库是经过音讯转发结束 cell 点击响应github的,比方 Texture[3],咱们不能逐一适配每个三方库。

咱们的收集计划的实质是创立了子类。关于子类来说,假定重写了一个父类中的办法,咱们能够经开源矿工过 super 去调用父类中的办法,并且无需关怀父类中的结束逻辑。若父类未结束,应swift什么意思该由体系去做音讯转发。

可是 – tableView:didSelectRowAtIndexPath: 办法是定义在 UITableViewDelegate 协议中的,无法运用 super 要害字,那咱们是否能够运用 runtime 相关接口结束向父类发送音讯呢?答案是必定的。

runtime 供应了 objc_msgSendSuper 的接口,定义如下:

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nongithub永久回家地址null op, …)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
super:objc_super 类型的结构体信息;
op:giti轮胎要调用的 selector;
…:selector 的相关参数。

究竟的音讯处理逻辑如下:

  • (void)tableView:(UITableView *)tableapple idView didSelectRowAtIndexPath:(NSIndexPath *)indeswift结算体系xPath {
    SEL methodSelector = @selector(tableView:didSeapp storelectRowAtIndexP开源阅览athswift怎样读:);
    [SADelegateProxy invokeWithTarget:self selector:methodSelecswiftertor scrollView:tableView indexPath:indegithub中文官网网页xPath];

}

  • (apple watchvoid)invokeWithTargegithub敞开私库t:(NSObject *)target selector:(SELapple tv)selecgithub永久回家地址tor scrollView:(UIScrapple id密码重置ollView *)scrollView indexPath:(NSIndexPath *)indexPath {
    Classswiftkey originalClgithub中文社区ass = N开源节流SClassFromString(target.sensorsdatagithub下载_className) ?: target.superclass;
    struct objc_sapple id密码重置uper targetSuperGit = {
    .receiver = target,
    .super_classgithub敞开私库 = orgitiiginal开源是什么意思Class
    };
    // 音讯发送给原始类
    v开源节流oid (*func)(struct objc_super *, SEL, id, id) = (void *)&objc_msgSendSuper;
    func(&targetSuper, selector, scrollView, indexPath);

    // 当 target 和 delegate 不相等时为音讯转发, 此刻无swift什么意思需重复收集开源阅览作业
    if (target != scrollView.delegate) {
    return;
    }
    // 作业收集
    // …

}

三、总结

本文介绍了如何以新建子类的办法收集 cell 点击作业,并对 KVO 场景gitlab进行了兼容,一同也对 NSProxy 的场景进行了支撑,并结束了向父类发送音讯,该方开源我国案的具体结束能够从神策剖析 iOS SDK 源码[4]中找到。假定大家有更好的主见,欢迎参加开源社区一同GitHub谈论。

参阅文献:

[1]developer.apple.com/swift言语library/arc…

[2]gi开源众包thub.com/ReactiveX/R…

[3]github.com/TextureGrou…

[4]ggithub打不开ithub.co开源软件m/sensorsdata…