2020-12-06

最近准备进一步重构某几个页面,从结构上讲用的是MVVM,较为清晰明了,同时也不至于所有代码都集中在UIViewController里导致一团麻,但是随着一个页面功能的更改替换,还有业务统计代码等等的加入,终究还是忍不住想要稍微整理整理。

由于页面多为UITableView且页面多复用,各种统计或者页面差异展示,所以想如果可以多个代理均可按先后顺序执行,那就可以将一些统计或者页面差异展示跟其它正常业务进行分离,提升项目代码的可维护性。

其实上面说的就是透明的分布式消息传递也可称为消息分发器,所以在这里要介绍一下非NSObject的子类,而是另一个基类NSProxy,从命名上看,就是代理,即设计模式中的代理模式,NSProxy是一个抽象超类,实现根类要求的基础方法,包括协议中定义的方法,可作为其他对象替身(甚至不存在的对象),NSProxy负责把消息转发给真正的target的代理类,利用这个特性,NSProxy就能透明的分布式消息传递

NSProxy

从苹果官方文档的介绍可以看到,他就是一个替代,帮忙转发消息给具体实现的类,或者懒加载一个较大的实例

Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object

作为抽象类不能直接使用NSProxy,它不提供初始化的方法,并且在接收到它没有响应的任何消息时引发异常,因此只能继承并在具体的子类提供初始化或创建方法,并实现forwardInvocation:和methodSignatureForSelector:

所以其实只要实现了这两个方法即可,至于其它的一些方法,未实现均会走到消息转发这一步,若是实现其它譬如respondsToSelector:也无妨

NSProxy与NSObject的消息传递的不同

NSObject收到消息会先去缓存列表查找SEL,若是找不到,就到自身方法列表中查找,依然找不到就顺着继承链进行查找,依然找不到的话,那就是unknown selector,进入消息转发程序

1.+(BOOL)resolveInstanceMethod:其返回值为Boolean类型,表示这个类是否通过class_addMethod新增一个实例方法用以处理该unknown selector,也就是说在这里可以新增一个处理unknown selector的方法,若不能,则继续往下传递(若unknown selector是类方法,那么调用+(BOOL)resolveClassMethod:)

2.- (id)forwardingTargetForSelector:这是第二次机会进行处理unknown selector,即转移给一个能处理unknown selector的其它对象,若返回一个其它的执行对象,那消息从id objc_msgSend ( id self, SEL op, ...)重新开始,若不能,则返回nil,并继续向下传递,最后的一次消息处理机会(3 与 4 配套)

3.- (NSMethodSignature *)methodSignatureForSelector:返回携带参数类型、返回值类型和长度等的selector签名信息NSMethodSignature对象,Runtime内部会基于NSMethodSignature实例构建一个NSInvocation对象,作为回调- (void)forwardInvocation:的入参

4.- (void)forwardInvocation:这一步可以对传进来的NSInvocation进行一些操作,它主要在对象之间或者应用程序之间存储和转发消息(命令模式的实现),灵活性很高,譬如修改target、参数、甚至返回值,有兴趣可以去了解下NSInvocation

对于NSProxy就没有这么复杂了,接收到unknown selector后,直接回调- (NSMethodSignature *)methodSignatureForSelector:和- (void)forwardInvocation:, 消息转发过程简单的很多

消息分发器

基于以上,实现上也很简单,就是将具体接收消息的实际对象保存在容器中,并按顺序让它们接收消息即可

/**

Normal forwarding 的第一步,也是消息转发的最后一次机会--这个针对NSObject, 对于 NSProxy 未实现立马走这里

消息获得函数的参数和返回值类型,即返回一个函数签名

@param sel selector 方法选择子

@return NSMethodSignature 函数签名

        返回nil,Runtime 则会发出 -doesNotRecognizeSelector: 消息,程序 crash

        返回了NSMethodSignature,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象

*/-(nullable NSMethodSignature*)methodSignatureForSelector:(SEL)sel{for(id objinself.targets){if([obj respondsToSelector:sel]){return[obj methodSignatureForSelector:sel];}}return[supermethodSignatureForSelector:sel];}

/**

可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一新的目标执行

@param invocation 对一个消息的描述,包括 selector 以及参数等信息

*/-(void)forwardInvocation:(NSInvocation*)invocation{BOOL invoked=NO;for(id objinself.targets){if([obj respondsToSelector:invocation.selector]){[invocation invokeWithTarget:obj];invoked=YES;}}if(!invoked){[superforwardInvocation:invocation];}}

使用

 a. 伪多继承

// dispathProx 同时可以执行 LYAnimal 和 LYPerson 的方法,看起来像是多继承。。。其实假的啦id dispathProx=ly_dispatchProxy([LYAnimalnew],[LYPersonnew],nil);[dispathProx talk];[dispathProx walk];[dispathProx pushers];[dispathProx shovel];

 b.协议多分发 即 消息分发用的最多的一种

// ⚠️ self 持有 delegateProxy,delegateProxy 内部持有传入的消息接收对象,里面也有 self, 所以使用 ly_weakObject(self),改为 weak 引用// ⚠️ 当有返回值时,后面的返回值会覆盖前面的self.delegateProxy=(LYDispatchProxy*)ly_dispatchProxy([LYTableDelegate new],ly_weakObject(self),nil);// 可在LYTableDelegate实现一些统计业务或者差异展示的设置处理....talbe.delegate=self.delegateProxy;talbe.dataSource=self.delegateProxy;

// 或者也可以如下用法,凭想象吧。。。其实肚子饿了,所以咕咕叫,不想想了LYDispatchProxy*btnTargetProxy=ly_dispatchProxy(XXX0,XXX1,nil);[btn addTarget:btnTargetProxy action:NSSelectorFromString(@"xxxx")forControlEvents:UIControlEventTouchUpInside]

你可能感兴趣的:(2020-12-06)