美文网首页iOS底层原理整理
iOS runtime(2)-class结构和消息转发机制

iOS runtime(2)-class结构和消息转发机制

作者: 周灬 | 来源:发表于2019-07-18 17:10 被阅读0次

    1. class结构

    一. class结构

    其实类对象和元类对象的结构是相同的,元类对象是一种特殊的类对象.由于类对象和元类对象结构相同,但我们为什么感觉类对象只有对象方法列表,元类对象只有类对象列表呢,原因是不需要的数据都变为nil.
    下图是class结构图

    Class结构.png

    二. class_rw_t(可修改的)

    class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容.

    class_rw_t的结构.png

    三. class_ro_t(不可修改的)

    class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容.

    class_ro_t的结构图.png

    注:在runtime的过程中会将ro中的methods和分类中的methods合并到rw中的methods中,class的bits原来的指向是指向ro的,在runtime的过程中bits的指向由指向ro改变成指向rw

    四. method_t

    nmethod_t的结构体是对方法\函数的封装.

    struct method_t{
          SEL name;      //函数名
          const char *types;    //编码(返回值类型、参数类型)
          IMP imp;         //指针函数的指针(函数地址)
    };
    

    IMP代表函数的具体实现

    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull,...);
    

    SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

    • 可以通过@selector()sel_registerName()获得.
    • 可以通过sel_getName()NSStringFromSelector()转成字符串.
    • 不同类中相同名字的方法,所对应的方法选择器是相同的.
    typedef strct objc_selector *SEL;
    

    types包含了函数返回值、参数编码的字符串

    返回值 参数1 参数2 ..... 参数n

    Type Encoding

    Type Encoding.png

    2. 方法缓存

    Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度.

    方法缓存图.png

    散列表的原理:将key传递并计算出一个index(索引).

    散列表原理.png

    用key(selector)的值&_mask就是所需要的imp,如果取值&后selector和key值不相等,_mask-1后再做&的操作.存储的时候已经&_mask计算好了缓存在第几个位置,如果在计算的时候存储的位置有方法缓存,会做_mask-1后再&的操作.(_mask有个初始值,如果容量不足可以扩容,扩容的时候清空缓存).

    3. 消息转发机制

    一. objc_msgsend

    OC的方法调用,也叫做消息机制,给方法调用者发送一条消息.

    OC中的方法调用,其实都是转换成objc_msgsend函数调用的.

    • objc_msgsend的流程大致分为3个阶段:
      1.消息发送.
      2.动态方法解析.
      3.消息转发.

    objc_msgSend执行流程 – 源码跟读流程

    objc_msgSend执行流程.png

    二. objc_msgSend执行流程01-消息发送

    消息发送.png

    如果调用的是父类的方法,会把方法缓存到当前类,如果调用的是自己的方法,会把方法的缓存到自己的类中.

    三. objc_msgSend执行流程02-动态方法解析

    动态解析的流程图.png
    1. 开发者可以实现以下方法,来动态添加方法实现.
    • +(BOOL)resolveInstanceMethod:(SEL)sel.
    • +(BOOL)resolveClassMethod:(SEL)sel.
    1. 动态解析过后,会重新走“消息发送”的流程.
    • 从receiverClass的cache中查找方法”这一步开始执行.

    demo:

    #import <Foundation/Foundation.h>
    
    @interface CSPersion : NSObject
    - (void)test;
    @end
    
    #import "CSPersion.h"
    #import <objc/runtime.h>
    
    void otherC(id self, SEL _cmd) {
        NSLog(@" %@-%s-%s",self,sel_getName(_cmd),__func__);
    }
    
    @implementation CSPersion
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
      
        if (sel == @selector(test)) {
            struct method_t *method = (struct method_t*)class_getInstanceMethod(self,@selector(other));
            class_addMethod([self class], sel, method->imp, method->types);
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel {
      
        if (sel == @selector(test)) {
            struct method_t *method = (struct method_t*)class_getInstanceMethod(self,@selector(other));
            class_addMethod([self class], sel,method_getImplementation(methd), method_getTypeEncoding(method));
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    
        if (sel == @selector(test)) {
            class_addMethod([self class], sel, (IMP)otherC, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    - (void)test {
        NSLog(@"test ...");
    }
    - (void)other {
        NSLog(@"other...");
    }
    @end
    

    我们有三种方式进行方法动态解析,还是建议用第二种方式,第二种方式比较清晰.

    四. objc_msgSend的执行流程03-消息转发

    消息转发的意思是把消息发送给别人,交给能够处理消息的人.
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;方法返回的签名types不为nil时,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation ;.
    生成NSMethodSignature

    NSMethodSignature  *signature = [[NSMethodSignature signatureWithObjCTypes:"i@:i"]];
    NSMethodSignature *signature = [[MJStudent alloc] init] methodSignatureForSelector:@selector(test:)];
    
    消息转发流程.png
    demo
    @interface Cat : NSObject
    - (int)test:(int)age;
    @end
    
    @implementation Cat
    - (int)test:(int)age {
        NSLog(@"%s",__func__);
        return age * age;
    }
    @end
    
    /** 消息发送 */
    @interface Student : NSObject
    - (void)test:(int)age;
    @end
    
    @implementation Student
    
    //+ (BOOL)resolveInstanceMethod:(SEL)sel
    //{
    //    class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
    //}
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(test:)) {
            
            // 测试一
    //        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
            
            // 测试二
    //        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
            
            // 测试三
    //        return [[[Cat alloc] init] methodSignatureForSelector:aSelector];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        // 参数顺序:receiver、selector、other arguments
        
        // 测试一
    //    int age;
    //    [anInvocation getArgument:&age atIndex:2];
    //    NSLog(@"%d", age + 10);
        
        // 测试二
        // anInvocation.target == [[MJCat alloc] init]
        // anInvocation.selector == test:
        // anInvocation的参数:15
    //    [[[Cat alloc] init] test:15];
        
        // 测试三
        [anInvocation invokeWithTarget:[[Cat alloc] init]];
        int ret;
        [anInvocation getReturnValue:&ret];
        NSLog(@"%d", ret);
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 2.消息转发
            Student *stu = [[Student alloc] init];
            [stu test:10];
        }
        return 0;
    }
    

    五. objc_msgSend-类方法消息转发

    + (id)forwardingTargetForSelector:(SEL)aSelector为nil时,会继续调用+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,如果methodSignatureForSelector为nil,则会报一个非常经典的错误doesNotRecognizeSelector,我们可以看出从方法我们只有在methodSignatureForSelector为nil时才会报错.

    @interface CSCat : NSObject
    + (void)test;
    - (void)test;
    @end
    
    @implementation CSCat
    + (void)test {
        NSLog(@"%s", __func__);
    }
    
    - (void)test {
        NSLog(@"%s", __func__);
    }
    @end
    
    /** 类方法的转发过程 */
    @interface CSPerson : NSObject
    + (void)test;
    @end
    
    @implementation CSPerson
    
    + (id)forwardingTargetForSelector:(SEL)aSelector {
        // objc_msgSend([[MJCat alloc] init], @selector(test))
        // [[[MJCat alloc] init] test]
        // 该方法显示与注释后有不同的结果
    //    if (aSelector == @selector(test)) {
    //        return [[CSCat alloc] init];
    //    }
        
        return [super forwardingTargetForSelector:aSelector];
    }
    
    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
    
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"1123");
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [CSPerson test];
        }
        return 0;
    }
    
                                想了解更多iOS学习知识请联系:QQ(814299221)

    相关文章

      网友评论

        本文标题:iOS runtime(2)-class结构和消息转发机制

        本文链接:https://www.haomeiwen.com/subject/ddullctx.html