iOS - 消息发送的完备流程

手机游戏开发者 2024-10-2 19:42:18 33 0 来自 中国
写在前面

在OC内里,调用对象的某个方法着实就是给这个对象发送一个消息,这个过程我们把它分为三大阶段,分别为:消息发送阶段、动态剖析阶段、消息转发阶段,本文将细细分析这三个阶段,但是在分析这三大阶段之前我们需要先回顾一下Class的结构。
Class结构

苹果源码最新下载地点请点击:苹果源码
在objc-runtime-new.h中可以看到objc_class结构如下:
struct objc_object {    Class isa;};struct objc_class : objc_object {      Class superclass;       cache_t cache;  // 方法缓存      class_data_bits_t bits; // 获取详细类信息      class_rw_t *data() const {         return bits.data();     }    ...... };从上面的结构我们可以看到有一个类cache_t,这个类就是专门拿来做方法缓存干系的类,结构如下:
struct cache_t {    struct bucket_t *buckets();    mask_t occupied();    mask_t mask();};struct bucket_t {    cache_key_t _key;    IMP _imp;};class_data_bits_t用于获取详细的类信息,结构如下:
#define FAST_DATA_MASK          0x00007ffffffffff8ULstruct class_data_bits_t {    uintptr_t bits;public:    class_rw_t* data() {        return (class_rw_t *)(bits & FAST_DATA_MASK);    }};// readWrite:可读可写struct class_rw_t {    uint32_t flags;    uint32_t witness;    Class firstSubclass;    Class nextSiblingClass;    class_rw_ext_t *ext;    const class_ro_t *ro;};struct class_rw_ext_t {  class_ro_t *ro;  method_array_t methods;// 方法列表  property_array_t properties; // 属性列表  protocol_array_t protocols; // 协议列表  char *demangledName;   uint32_t version;}// readOnly:只读struct class_ro_t {    uint32_t flags;    uint32_t instanceStart;    uint32_t instanceSize; #ifdef __LP64__    uint32_t reserved;#endif    const uint8_t * ivarLayout;    const char * name;  // 类名    method_list_t * baseMethodList;    protocol_list_t * baseProtocols;    const ivar_list_t * ivars;  // 成员变量列表    const uint8_t * weakIvarLayout;    property_list_t *baseProperties;};分析到这里,Class结构我们已相识清楚,接下来就是调用对象的方法来研究一下消息发送的完备流程。
消息发送阶段

在OC内里,调用对象的某个方法就是给这个对象发送一条消息,这里我们新建一个Person类,以[person personRun]为例来看看消息发送阶段的流程。
在【iOS重学】方法缓存cache_t的分析这篇文章中我们重要分析了方法缓存,发起各人先看一下缓存可以资助我们明白接下来的流程。
我们知道OC中的方法调用着实就是转成objc_msgSend()函数的调用(load方法除外),如下:

1.png
// 消息发送阶段源码跟读序次1. objc-msg-arm64 汇编文件    ENTRY _objc_msgSend    b.le LNilOrTagged    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached    .macro CacheLookup    CacheHit // 掷中缓存    MissLabelDynamic // 着实就是__objc_msgSend_uncached    STATIC_ENTRY __objc_msgSend_uncached    MethodTableLookup    .macro MethodTableLookup    bl _lookupImpOrForward2. objc-runtime-new.mm 文件    lookupImpOrForward    getMethodNoSuper_nolock(curClass, sel)    curClass = curClass->getSuperclass()    cache_getImp(curClass, sel) // 从父类缓存内里查找    log_and_fill_cache // 缓存方法到消息吸取者这个类消息发送的流程图如下:

2.png
我们来验证一下是否真的缓存了调用的方法:

未调用personRun时,我们查一下在Person类的cache内里是否能找到personRun方法缓存:
Person *person = [[Person alloc] init];mj_objc_class *personClass = (__bridge  mj_objc_class *)[Person class];NSLog(@"%@ %p",NSStringFromSelector(@selector(personRun)), personClass->cache.imp(@selector(personRun)));打印结果如下:
2022-04-10 13:11:30.367394+0800 RuntimeDemo[88049:12459843] personRun 0x0结果分析:在cache并没有找到personRun的IMP。
调用personRun之后,我们查一下Person类的cache内里是否能找到personRun方法缓存:
Person *person = [[Person alloc] init];[person personRun];mj_objc_class *personClass = (__bridge  mj_objc_class *)[Person class];NSLog(@"%@ %p",NSStringFromSelector(@selector(personRun)), personClass->cache.imp(@selector(personRun)));打印结果如下:
2022-04-10 13:13:30.294687+0800 RuntimeDemo[88074:12461806] personRun 0x78cc0结果分析:调用personRun之后,会把personRun缓存到方法缓存内里
动态方法剖析阶段

当第一阶段【消息发送阶段】没有找到方法实现就会进入第二阶段【动态方法剖析阶段】。
// 动态方法剖析阶段源码跟读序次1. objc-runtime-new.mm 文件  resolveMethod_locked  resolveInstanceMethod 或 resolveClassMethod  lookupImpOrNilTryCache  _lookupImpTryCache  lookupImpOrForward动态方法剖析的流程图如下:

动态方法剖析流程

根据+ (BOOL)resolveInstanceMethodSEL)sel (实例方法调用这个) 或+ (BOOL)resolveClassMethodSEL)sel(类方法调用这个)来做动态方法剖析,然后重新走一遍消息发送的流程(从消息担当者的方法缓存内里开始继承往下实行)
动态方法剖析代码

- (void)otherRun {  NSLog(@"%s",__func__);}+ (BOOL)resolveInstanceMethodSEL)sel {    if (sel == @selector(personRun)) {        Method otherMethod = class_getInstanceMethod(self, @selector(otherRun));        IMP imp = class_getMethodImplementation(self, @selector(otherRun));        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));        return  YES;    }    return [super resolveInstanceMethod:sel];}消息转发阶段

假如前面的两个阶段都没有实现,就会继承进入【消息转发】的流程。
// 消息转发阶段的源码跟读序次1. objc-msg-arm64 汇编文件  forward_imp = _objc_msgForward_impcache  STATIC_ENTRY __objc_msgForward_impcache  b __objc_msgForward  ENTRY __objc_msgForward  ENTRY __objc_msgForward_stret  __objc_forward_stret_handler2. objc-runtime-new.mm 文件  void *_objc_forward_stret_handler = (void *)objc_defaultForwardStretHandler;3. CoreFoundation 框架  __forwarding__ // 不开源消息转发的流程图如下:

4.png 消息转发流程

消息转发流程也分为了两步:
第一步:forwardingTargetForSelector:方法是指把相应这个方法的对象转发给其他的对象,那么消息担当者就发生了变化,会重新调用一遍objc_MsgSend(消息担当者,SEL)流程
第二步:forwardingTargetForSelector: 方法返回为nil,继承查抄methodSignatureForSelector:是否返回了一个方法署名,然后去实行forwardInvocation:方法
消息转发流程干系代码实现


  • 实例方法流程
// 第一步- (id)forwardingTargetForSelectorSEL)aSelector {    if (aSelector == @selector(personRun)) {        return [[Student alloc] init]; // 这里返回的是你想把这个消息转发给哪个对象    }    return [super forwardingTargetForSelector:aSelector];}// 第二步- (NSMethodSignature *)methodSignatureForSelectorSEL)aSelector {    if (aSelector == @selector(personRun)) {        // ⚠️:这里的方法署名的types不能任意写 因为这里的方法署名决定了下一步的NSInvocation的返回值、参数范例等        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];    }    return [super methodSignatureForSelector:aSelector];}- (void)forwardInvocationNSInvocation *)anInvocation {     [anInvocation invokeWithTarget:[Student new]];  // 在这个方法里可以做任何我们想做的事变}
类方法流程
// 第一步+ (id)forwardingTargetForSelectorSEL)aSelector {    if (aSelector == @selector(personTest)) {        return [Student class];    }    return [super forwardingTargetForSelector:aSelector];}// 第二步+ (NSMethodSignature *)methodSignatureForSelectorSEL)aSelector {    if (aSelector == @selector(personTest)) {        return [Student methodSignatureForSelector:aSelector];    }    return [super methodSignatureForSelector:aSelector];}+ (void)forwardInvocationNSInvocation *)anInvocation {    [anInvocation invokeWithTarget:[Student class]];}+ (void)doesNotRecognizeSelectorSEL)aSelector {    NSLog(@"%s",__func__);}关于NSInvocation类

@interface NSInvocation : NSObject+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;// 方法署名@property (readonly, retain) NSMethodSignature *methodSignature;// retain所有参数 防止参数被dealloc- (void)retainArguments;// 参数是否都被retained@property (readonly) BOOL argumentsRetained;// 消息吸取者@property (nullable, assign) id target;// 方法名@property SEL selector;// 获取返回值- (void)getReturnValue:(void *)retLoc;// 设置返回值- (void)setReturnValue:(void *)retLoc;// 获取idx的参数- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;// 设置idx的参数- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;// 调用- (void)invoke;- (void)invokeWithTarget:(id)target;@end各人有爱好的话可以去试试NSInvacation的使用。
末了

假如按照上面的三大流程都走完之后依然没有找到相应的方法实现,那这个调用末了就会调用doesNotRecognizeSelecto:抛出非常,假如错误请多多指教,末了接待去我的个人技术博客逛逛。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:52, Processed in 0.150288 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表