Class结构+消息发送+方法解析+消息转发

一、Class 结构

1.1 objc_class(类)

上图是 Class 的总体结构图,其中 OC 中的 Class 直接转换为 C++ 中的对应的结构体objc_classobjc_class中主要包含:

  • isa指针:对象的isa指向类,类的 isa指向元类,元类的isa指向根元类,根元类的isa指向自身。
  • supreclass指针:指向父类。
  • cache 方法缓存列表,下面第二小节会重点介绍。
  • bits 包含着具体的类信息。

1.2 cache_t (方法缓存)

cache_t 主要用来缓存方法,内部使用散列表(也称哈希表)来缓存曾经调用过的方法,以SEL作为 key ,以IMP作为Value ,通过空间换时间的思想提高方法查找速度。每次调用方法前,首先去cache_t中查找通过散列表特性查找是否有该方法。存在则直接调用,否则去class_rw_t中的methods 遍历查找该方法,调用并将该方法缓存到cache_t中,以便提高下次调用效率。

1.3 class_rw_t (类信息)

bits & FAST_DATA_MASK 可获取类信息,对应的类型为class_rw_t(rw表示read、write,即可读可写)。

  • class_ro_t里面的 baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的( ro 即read only),仅包含了类的初始内容,未包含分类的信息。当首次加载类的时候,会将class_rw_t中的方法列表加载到二维数组methods中,放在数组末尾,具体可以查看Category底层分析
  • class_rw_t里面的methodspropertiesprotocols是二维数组,是可读可写的,包含了类的初始内容以及分类的内容。

1.4 method_t(方法结构体)

method_t是对方法或函数的封装。主要由三部分组成:

  • IMP代表函数的具体实现
  • SEL代表方法或函数名,也做选择器
  • types包含了函数返回值以及参数编码的字符串,苹果官方文档搜索Type Encoding可查看相关信息。如[self test]中,test 方法对应的types值为v16@0:8,v 表示 void@表示id:表示SEL, 因为OC方法调用底层会默认传两个参数,一个是id类型self,一个是SEL类型_cmd。另外,16 表示参数的总长度,0 和 8 分别表示第一个参数和第二个参数的偏移值。

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。如NSLog(@"%s",@encode(id)) 输出结果为@

二、消息发送

OC 中的方法调用,其实都是转换为 objc_msgSend 函数的调用,该函数默认接收两个参数:一个是id类型的消息接收者,另一个是SEL类型_cmdobjc_msgSend的执行流程可以分为三个阶段,如果三个阶段都没有找到合适的方法进行调用,就会报一个经典错误unrecognized selector sent to instance

  • 消息发送阶段
  • 动态方法解析阶段
  • 消息转发阶段
  • 1、消息发送阶段首先判断消息接收者是否为 nil ,为 nil 直接退出;否则,去消息接收者的cache中寻找是否存在发方法。
  • 2、消息接收者的cache中存在直接调用,不存在则去接收者的类信息class_rw_t中查找,查找到放入到消息接收者的cache中;否则,去消息接收者父类cache中查找;
  • 3、消息接收者父类cache中存在该方法,直接调用;不存在则去接收者的父类类信息class_rw_t中查找,查找到放入到消息接收者父类的cache中(从图中看应该是放入消息接受者的cache中,和此处矛盾);否则,去消息接收者父类的父类cache中查找;即循环执行第三步,也即上图红色框圈入的部分。
  • 4、如果一直找到最基层的父类中,依然没有找到方法,则会进入动态方法解析阶段。

子类没有实现方法会调用父类的方法,并且将父类方法加入到子类自己的cache 里。?????

三、动态方法解析

如果可以找到方法的实现,就进入消息转发阶段;否则,调用+resolveInstanceMethod:+resolveClassMethod:,在上述两个方法内动态添加方法,防止崩溃,之后返回YES,重新返回消息发送阶段;如果没有动态添加方法,直接返回YES 或 NO,依然无法找到方法实现,进而发生奔溃。注意:上述两个方法的返回值都是 BOOL 类型,返回值只是标记是否动态解析方法,无论返回 YES 还是 NO,对消息执行流程无任何影响。

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));
        // 动态添加test方法的实现
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
+ (void)other{
    NSLog(@"%s", __func__);
}

四、消息转发阶段


所谓的消息转发是指:自身无法处理消息,转发给他人来处理。当方法解析阶段找到对应方法实现,就会来到消息转发阶段。消息转发阶段有三次拦截消息的机会,即下面三个方法,三个方法都有对应的对象方法和类方法。

  • forwardingTargetForSelector
  • methodSignatureForSelector
  • forwardInvocation
4.1 forwardingTargetForSelector(返回方法处理者)
@interface Person : NSObject
- (void)test;
@end

@implementation Person
//如果不实现该方法默认返回nil,则会进一步调用`methodSignatureForSelector `方法;否则,由该方法返回的对象处理消息。
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        //如果返回nil,则直接调用methodSignatureForSelector方法
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end
Person *person = [[Person alloc] init];
[person test];

如果不实现forwardingTargetForSelector 方法默认返回nil 或直接在该方法中返回 nil ,则会进一步调用methodSignatureForSelector方法;否则,由该方法返回的对象处理消息,即传入返回的对象,直接调用objc_msgSend(返回值, SEL)方法。上面的Person类中没有test方法的实现,forwardingTargetForSelector: 方法中返回 [[Cat alloc] init] 对象处理。

4.2 methodSignatureForSelector(返回方法签名)

如果方法methodSignatureForSelector返回的方法签名为 nil,则直接崩溃;如果返回不为 nil ,则说明想处理消息,则进一步调用forwardInvocation方法。返回的方法签名会传递到forwardInvocation方法中的NSInvocation对象中。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
//这里的方法参数、返回值和`forwardInvocation`中的NSInvocation对应
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}
4.3 forwardInvocation(方法调用)

当来到该方法后,可以在这里做任何事情。其中传入的参数anInvocation对象封装的是一个方法的调用,包含了方法调用者(anInvocation.target)、方法名(anInvocation.selector)、方法参数([anInvocation getArgument:NULL atIndex:0])。

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"1123");
}
4.4 doesNotRecognizeSelector,发生崩溃

如果在为消息解析阶段找不到方法实现,且上述三个方法没有做任何处理,会直接调用doesNotRecognizeSelector方法 ,最终发生崩溃。

4.5 消息转发实际开发应用(防止找不到方法崩溃)

消息转发机制笔者在实际开发中有用到,主要是用来处理方法找不到,发生崩溃问题。思路主要是在+load方法中,交叉替换forwardingTargetForSelector方法,动态创建新的方法处理对象,并在该对象中动态添加方法。具体实现如下:

@implementation NSObject (Exception)
+ (void)load {
    //防止外部手动调用load方法,固load方法中最好都要写上dispatch_once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
           [objc_getClass("NSObject") swizzleMethod:@selector(forwardingTargetForSelector:) swizzledSelector:@selector(replace_forwardingTargetForSelector:)];
            
        }
    });
}
- (id)replace_forwardingTargetForSelector:(SEL)aSelector{
    NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
    if ([self respondsToSelector:aSelector] || signature) {
        return [self replace_forwardingTargetForSelector:aSelector];
    }
    return [NSObject createFakeForwardTargetObject:self selector:aSelector];
}
+ (id)createFakeForwardTargetObject:(id)aTarget selector:(SEL)aSelector{
    if ([[NSString string] respondsToSelector:aSelector]) {
        NSString *szTarget = nil;
        if ([aTarget isKindOfClass:[NSNumber class]]) {
            szTarget = [NSString stringWithFormat:@"%@", aTarget];
        }
        
        if (szTarget) {
            return szTarget;
        }
    }
    FakeForwardTargetObject *fakeTaget = [[FakeForwardTargetObject alloc] initWithSelector:aSelector];
    return fakeTaget;
}
@end
@implementation FakeForwardTargetObject
- (instancetype)initWithSelector:(SEL)aSelector{
    if (self = [super init]) {
        if(class_addMethod([self class], aSelector, (IMP)fakeIMP, NULL)) {
            MCLog(@"add Fake Selector:[instance %@]", NSStringFromSelector(aSelector));
            NSString *string = [NSString stringWithFormat:@"[%s:%d行]",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
             showExceptionAlert(string);
        }
    }
    return self;
}
@end
id fakeIMP(id sender,SEL sel,...){
    return nil;
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容