iOS 使用 NSProxy 实现消息分发器

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

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

  其实上面说的就是 透明的分布式消息传递 也可称为 消息分发器 ,所以在这里要介绍一下非 NSObject 的子类,而是另一个基类 NSProxy,从命名上看,就是代理,即设计模式中的 代理模式NSProxy 是一个抽象超类,实现根类要求的基础方法,包括<NSObject>协议中定义的方法,可作为其他对象替身(甚至不存在的对象),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:也无妨

  • NSProxyNSObject 的消息传递的不同
      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 obj in self.targets) {
        if ([obj respondsToSelector:sel]) {
            return [obj methodSignatureForSelector:sel];
        }
    }
    return [super methodSignatureForSelector:sel];
}
/**
可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一新的目标执行

 @param invocation 对一个消息的描述,包括 selector 以及参数等信息
 */
- (void)forwardInvocation:(NSInvocation *)invocation {
    BOOL invoked = NO;
    for (id obj in self.targets) {
        if ([obj respondsToSelector:invocation.selector]) {
            [invocation invokeWithTarget:obj];
            invoked = YES;
        }
    }
    if (!invoked) {
        [super forwardInvocation:invocation];
    }
}
  • 使用

 a. 伪多继承

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

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

  // ⚠️ self 持有 delegateProxy,delegateProxy 内部持有传入的消息接收对象,里面也有 self, 所以使用 ly_weakObject(self),改为 weak 引用
  // ⚠️ 当有返回值时,后面的返回值会覆盖前面的
  self.delegateProxy = (LYDispatchProxy <UITableViewDataSource, UITableViewDelegate>*)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];

  业务做的多了,慢慢也有了更多的思考,但是无论是想什么,偶现在就是要吃烧烤,别的都不管 哼 哼 哼

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容