[iOS 理解] 消息机制 & NSProxy

消息机制是什么?NSProxy 意义何在?二者有何关联?下面会深入每一个调用发现其中的奥秘。

消息机制

务必结合 objc-msg-arm64.s 源码看,本文是解读。较旧的源码下载。注意:理解之前不要用官网最新源码学习,WWDC 2020 之后加了很多新特性,影响整体学习。

调用对象方法如 [receiver sel]; 会编译为 objc_msgSend(receiver, sel) 的调用。

objc_msgSend(receiver, sel) 汇编解析

// arm64架构。x0 = receiver, x1 = sel, x16 = isa
receiver nil check/tagged pointer check
 if nil return
 if tagged 根据 tagged 规则获取所属类的 isa -> x16
 if not tagged, get receiver isa (isa -> x16)

CacheLookup NORMAL (NORMAL 参数是执行找到的 IMP) (macro)
 从 isa 的方法缓存表中寻找 SEL 的 bucket_t:
 找到了 -> CacheHit:(macro)
  call IMP (br x17)
 没找到 -> CheckMiss:(macro)
  call __objc_msgSend_uncache
   MethodTableLookup (macro)
    备份寄存器(说明将调用一个C函数)
    call __class_lookupMethodAndLoadCache3
    返回值就是 IMP (mov x17, x0)
    还原寄存器
   call IMP (br x17)

如果是 objc_msgSendSuper,objc_msgSendSuper(objc_super*, sel)
从 [x0] 取出两个值 -> x0, x16,分别是 receiver,isa,直接给定 isa。
(不常用的 super2 是 isa->superclass;_stret 和 C 语言返回结构体规则相同,作参数)

__class_lookupMethodAndLoadCache3 函数解析

如果是第一次调用当前类的方法,调用 _class_initialize:
  递归初始化父类
  通过 objc_msgSend 调用 +initialize
  设置标志位,唤醒等待的线程

*查找当前类的方法表;查找继承链上所有父类cache、方法表
 找到非转发函数的 IMP,加入当前类缓存,返回
 没有找到,如果尝试且只尝试一次 [动态方法解析]
  如果是在解析类方法,当前类是元类
   查找SEL_resolveClassMethod 的 IMP(不尝试方法解析),设为IMP1
   如果 IMP1 不是通用转发函数,则用 objc_msgSend 调用 IMP1
    IMP1 内对 sel 添加实现,也可以不添加;返回值只用于打印提示
  如果是在解析实例方法
    SEL_resolveInstanceMethod,同上
  跳转至 * 重新查找,可能现在已经实现了
没有找到,设 IMP 为通用转发函数(__objc_msgForward_impcache),缓存,返回

__objc_msgForward_impcache 实现

 __objc_msgForward_impcache
   __objc_msgForward
     __objc_forward_handler

转发机制

CF 框架初始化时调用过 objc_setForwardHandler(___forwarding_prep_0___, ___forwarding_prep_1___)

___forwarding_prep_0___
  __forwarding__
   系统:receiver 你自己不能处理消息,让你解析也不解析,那你给我提供一个能处理消息的对象!
   - (id)forwardingTargetForSelector SEL
   应用:模拟多继承,返回持有的另一个类的对象,设为 helper
   如果返回一个非空非receiver对象,则 ret objc_msgSend 转发完成

   如果没有接收对象,构造一个 NSInvocation 让 receiver 转发
   构造 NSInvocation 需要方法签名(ret+args, 即 types)
   调用 [receiver methodSignatureForSelector:sel] 获取方法签名
   比如返回 helper 类中该 SEL 的签名
   然后 [receiver forwardInvocation:invocation]
   该函数中可以为所欲为了,比如让 helper invoke

   获取方法签名的 methodSignatureForSelector 会查找 receiver 类中 SEL 的方法签名,如果找不到则抛异常 doesNotRecognizeSelector,想想 一般是找不到的,不然怎么会走到转发机制。

NSProxy

系统库目前有两个 root class,分别是 NSObject 和 NSProxy,它们遵循 NSObject 协议复用了同一套内存引用计数、动态特性等等 objc 的内容。

NSProxy 用途就是它名字本身:代理人,即消息转发。转发场景例如:

  • Timer 等为打破循环引用,转发给弱引用的业务对象
  • 懒加载、伪多继承
  • 多进程通信,通过网络或其他手段调用其他资源

NSProxy 内部实现

NSProxy 的成员方法(NSObject 协议里的方法),里面的实现就是 直接开始构造 Invocation:先调用 methodSignatureForSelector: ,再调用 forwardInvocation: ,且这两个方法的默认实现都是直接抛异常。所以必须有子类实现这两个方法转发给业务 target。

如果调用一个不认识的 SEL(非 NSObject 协议里的方法),会通过正常的消息机制查找方法、动态解析、寻找 forwardingTarget、找不到才构造 invocation,然后抛异常。

NSProxy 可以被 NSObject 替代吗?

有了以上了解,好像 NSObject 也能承担 NSProxy 的功能?

但因为 NSObject 类实现了 NSObject 协议的方法,直接调用这些方法时会走到代理本身的方法中导致结果出错。虽然可以在子类中覆盖这些方法,但对于未知的 Category 中的方法就不可能全部覆盖了。有人说 NSProxy 也可以有 Category 啊?因为 NSProxy 设计的目的就是做为代理,真有 Category 的话就失去了它本身的意义,那时可以自定义一个新的 Proxy 根类出来。

再次反转,实际业务开发中比如 timer tick 事件,我们很明确的知道只需要转发一个 tick 方法,并且不可能出现上述问题,所以 NSProxy 在 99% 以上的场景中都可以被替代。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值