美文网首页
底层探索--runtime的本质

底层探索--runtime的本质

作者: 永断阎罗 | 来源:发表于2021-10-14 14:00 被阅读0次

    基本

    • Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
    • Objective-C的动态性是由Runtime API来支撑的
    • Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

    涉及基础

    • 类族:最基础的类型需实际去探究。

    • arm64架构之前,isa就是一个普通的指针,存储着对象、Class、Meta-Class对象的内存指针。

    • arm64之后isa指针的变化:

      • 对isa进行了优化,变成了一个共用体(union)结构,还是用位域来存储更多的信息(33位存类信息)。如下图:
      • 下图struct内部的: 数字表示所占位数(位域);(如下图,总共占64位,其中shiftcls : 33 从右到左占33位):所以Mask以后类地址和元类地址后三位一定是0,换算成16进制,则最后一位是0或8
      runtime之isa共用体.png

      位域详解


      runtime之isa位域详解.png

    位运算详解

    • 左移运算符 <<
      • x << n:x(二进制)向左移动n位(如:1 << 2,实际:Ob0001 << 2 = 0b0100 即十进制4)
    • 右移运算符 >>
      • x >> n:x(二进制)向右移动n位(如:8 >> 2,实际:Ob1000 >> 2 = 0b0010 即十进制2)
    • 或运算符 |
      • x | y:x(二进制)和y(二进制)只要对应的2个二进制位有一个为1时,则结果就为1(如:1 | 2,实际:0b0001 | 0b0010 = 0b0011 即十进制3)
    • 与运算符 &
      • x & y:x(二进制)和y(二进制)只有对应的2个二进制位都为1时,其结果才为1(如:15 & 2,实际:0b1111 & 0b0010 = 0b0010 即十进制2)
    • 取反运算符 ~
      • x:x(二进制)的所有二进制位都进行取反,0变1,1变0(如:2,实际:~0b0010 = 0b1101 即十进制13)

    位运算对应isa指针的实际应用

    例如isa位域的操作:

    实例:
    union Test { //共用体:开辟所有属性中占用最大字节的内存,每个属性使用共同的空间,值会覆写。
        char bits; //(char内存中占一个字节,占8位)
        struct { //相当于说明,占4位
            char isMan : 1;
            char isDead : 1;
            char age : 2;
        };
    } test;
    
    #define kIsMan (1 << 0)
    
    //设置对应isMan的值,而其他位不变
    - (void)setIsMan:(BOOL)isMan {
        if (isMan) {
            test.bits |= kIsMan;
        }else {
            test.bits &= (~kIsMan);
        }
    }
    
    //取出对应isMan的值
    - (BOOL)isMan {
        return !!(test.bits & kIsMan); //保证取出的值为BOOL值
    }
    

    方法本质

    • 类的底层结构


      类的结构体(真-最新).png
      • 其中的bits如最上位域所示,用64个字节,存取了非常多的信息
      • 其中class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容,(如特别说明:methods是二维数组,存储了本类和和分类的方法数组,其方法数组里才是对应的method_t);
      • 其中class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。
    • 方法的底层结构


      runtime之method的结构.png
      • 其中方法的types参数的编码表如下图所示:
        runtime之方法编码表.png
    • cache_t cache底层结构及原理说明:
      runtime之方法缓存结构.png
      • 原理说明:(注:_mask等于_buckets的长度-1)
        • 存储时:会@selector(方法名) & _mask,就可得到一个小于_buckets的长度的下标,如果此下标的里面为NULL则存取,反之(此下标-1 或 等于最大下标继续-1 ,直到等于此下标时结束)直到找到NULL存。
        • 读取是:同理@selector(方法名) & _mask,找到下标并判断里面内容的key是否等于@selector(方法名),有则返回,反之(此下标-1 或 等于最大下标继续-1 ,直到等于此下标时结束)如找到则返回,否则返回NULL。
        • 注意点:1. 当初始空间不够时,则重新开辟(初始值*2)的空间并清空内部所有缓存。2. &出来的下标可能相同,所以需要判断内部的key是否等于自身。

    消息转发

    参考文章

    [[MessageSend new] sendMessage:@"Hello"];
     //等同于
     objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello");
    

    步骤:

    1. 首先通过[Message new]对象的isa指针找打它对应的class。
    2. 在class的cache list 查找是否有sendMessage方法,有则返回,否则往下走。
    3. 在class的method list 查找是否有sendMessage方法。
    4. 如果没有就去它的superclass里继续查找(先cache、再method list)。
    5. 一旦查找到对应的函数方法,就去执行它的实现IMP。
    6. 如果一直没有找到,就执行消息转发。
    7. 消息转发机制:
      1. 动态方法解析:为指定方法(sel)添加方法实现(IMP)

         +(BOOL)resolveInstanceMethod:(SEL)sel; //对象方法-动态解析
         +(BOOL)resolveClassMethod:(SEL)sel; //类方法-动态解析
        
      2. 快速转发:为方法指定备援的接受者(即类对象)

          - (id)forwardingTargetForSelector:(SEL)aSelector; //针对对象方法
          + (id)forwardingTargetForSelector:(SEL)aSelector; //针对类方法
        
      3. 慢速转发:手动生成方法签名并转发给另一对象,分两步(方法签名+指定备胎对象)

         //针对对象方法
         - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
         - (void)forwardInvocation:(NSInvocation *)anInvocation;
         //针对类方法
         + (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
         + (void)forwardInvocation:(NSInvocation *)anInvocation;
        
        runtime之方法编码表.png
      4. 消息未处理:避免崩溃

           - (void)doesNotRecognizeSelector:(SEL)aSelector; //针对对象方法
           + (void)doesNotRecognizeSelector:(SEL)aSelector; //针对类方法
        

    注:越往下花费的代价越大。
    重点:对象方法和类方法传递的对象不同,一个传递类对象,一个传递元类对象。!!!!

    • 实例代码

       //1. 动态方法解析
       + (BOOL)resolveInstanceMethod:(SEL)sel {
           NSString *name = NSStringFromSelector(sel);
           if ([name isEqualToString:@"sendMessage:"]) {
               //C 的写法:则指定方法为C写法
               return class_addMethod(self, sel, (IMP)testFundation, "v@:");
               //OC的写法:则指定方法为OC写法
       //        return class_addMethod(self, sel, class_getMethodImplementation(self, @selector(testFundation)), "v@:");
           }
           return [super resolveInstanceMethod:sel]; //默认用父
       }
       
       //2. 快速转发
       - (id)forwardingTargetForSelector:(SEL)aSelector {
           NSString *name = NSStringFromSelector(aSelector);
           if ([name isEqualToString:@"sendMessage:"]) {
               TEst *test = [[TEst alloc] init];
               if ([test respondsToSelector:aSelector]) { //相应方法
                   return test;
               }
           }
           return [super forwardingTargetForSelector:aSelector];
       }
       
       //3.1 慢速转发-方法签名
       - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
           NSString *name = NSStringFromSelector(aSelector);
           if ([name isEqualToString:@"sendMessage:"]) {
               return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
           }
           return [super methodSignatureForSelector:aSelector];
       }
       
       //3.2 慢速转发(不实现方法签名,不会到这里来)你可以尽情的处理---用于避免崩溃或做其他操作。
       - (void)forwardInvocation:(NSInvocation *)anInvocation {
           SEL sel = [anInvocation selector];
           TEst *test = [[TEst alloc] init];
           if ([test respondsToSelector:sel]) { //相应方法
       //        anInvocation.target = test; //这样申明不行,下边得行
               [anInvocation invokeWithTarget:test];
           }else {
               [super forwardInvocation:anInvocation];
           }
       }
       
       //4. 消息未处理:避免崩溃
       - (void)doesNotRecognizeSelector:(SEL)aSelector {
           if (DEBUG) {
               NSLog(@"我没找到任何方法");
           }
       }
       
       //动态解析方法--采用OC的写法
       - (void) testFundation {
           NSLog(@"我倒这里来了--OC");
       }
       
       //动态解析方法--采用C的写法
       void testFundation() {
           NSLog(@"我倒这里来了--C");
       }
      

    super本质

    //objc_super2在最新官方源码中的结构体定义
    struct objc_super2 {
        id receiver; //接受者
        Class current_class; //当前类,内部实现是会取它的superclass
    };
    
    //作用:从消息接受者的superclass类开始找对象方法或类方法
    (void *)objc_msgSendSuper2)({
       (id)self,
       (id)class_getSuperclass(objc_getClass("Student"))
    }, sel_registerName("class"))
    
    //sel_registerName("class") == @selector(class)
    //结构体-两个成员:{self 和 class_getSuperclass("Student") == 父类}
    
    
    
    + (Class)class {
        return self;
    }
    
    - (Class)class {
        return object_getClass(self);
    }
    
    + (Class)superclass {
        return self->superclass;
    }
    
    - (Class)superclass {
        return [self class]->superclass;
    }
    
    
    //探究:super的本质
    NSLog(@"[self class] %@\n",[self class]);
    NSLog(@"[self superclass] %@\n",[self superclass]);
    NSLog(@"[super class] %@\n",[super class]);
    NSLog(@"[super superclass] %@\n",[super superclass]);
    
    /**
     //打印结果:
     testApp[1650:33448] [self class]       Student
     testApp[1650:33448] [self superclass]  Person
     testApp[1650:33448] [super class]      Student
     testApp[1650:33448] [super superclass] Person
    */
    
    //解释:接受者为self,方法为class,然class是NSObject的对象方法,而class内部代码为“object_getClass(self)”故在这种情况下:==[self class]
    
    • 总结:super的本质是,调用objc_msgSendSuper()消息发送,从消息接受者的superclass类开始找对象方法或类方法。

    探究isMemberOfClassandisKindOfClass

    • 源码

        //直接用self的元类对象和参数进行比较
        + (BOOL)isMemberOfClass:(Class)cls {
            return self->ISA() == cls;
        }
        
        //直接用self的类对象和参数比较
        - (BOOL)isMemberOfClass:(Class)cls {
            return [self class] == cls;
        }
        
        //用self及其父类的元类对象和参数进行比较
        + (BOOL)isKindOfClass:(Class)cls {
            for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
                if (tcls == cls) return YES;
            }
            return NO;
        }
        
        //用self及其父类的类对象和参数比较
        - (BOOL)isKindOfClass:(Class)cls {
            for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
                if (tcls == cls) return YES;
            }
            return NO;
        }
      
    • 实际运用

        //前提:Student 是 Person的子类
        
        NSLog(@"-isKindOfClass %d \n",[self isKindOfClass:[Person class]]); //1 //Student是Person的子类
        NSLog(@"-isMemberOfClass %d \n",[self isMemberOfClass:[Person class]]); //0 是直接[self class] 比较参数,故Student不匹配Person
        
        NSLog(@"+isKindOfClass %d \n",[[self class] isKindOfClass:[Person class]]); //0 因为类对象和元类对象不匹配
        NSLog(@"+isMemberOfClass %d \n",[[self class] isMemberOfClass:[Person class]]); //0 因为类对象和元类对象不匹配
        
        NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:[Person class]]); //0 因为类对象和元类对象不匹配
        NSLog(@"+isMemberOfClass %d \n",[NSObject isMemberOfClass:[Person class]]); //0  因为类对象和元类对象不匹配
        
        NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:[NSObject class]]); //1 特殊---因为按源码最终的superclass是类对象[NSObject class],故可以相等
        NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:object_getClass([Person class])]); //0  因为类对象和元类对象不匹配
        NSLog(@"+isMemberOfClass %d \n",[NSObject isMemberOfClass:object_getClass([Person class])]); //0  因为类对象和元类对象不匹配
      

    Runtime部分常用方法介绍

      • Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes):动态创建一个类(参数:父类,类名,额外的内存空间)
      • void objc_registerClassPair(Class cls):注册一个类(要在类注册之前添加成员变量
      • void objc_disposeClassPair(Class cls):销毁一个类
      • Class object_getClass(id obj):获取isa指向的Class
      • Class object_setClass(id obj, Class cls):设置isa指向的Class
      • BOOL object_isClass(id obj):判断一个OC对象是否为Class
      • BOOL class_isMetaClass(Class cls):判断一个Class是否为元类
      • Class class_getSuperclass(Class cls):获取父类
    • 成员变量
      • Ivar class_getInstanceVariable(Class cls, const char *name):获取一个实例变量信息
      • Ivar *class_copyIvarList(Class cls, unsigned int *outCount):拷贝实例变量列表(最后需要调用free释放)
      • void object_setIvar(id obj, Ivar ivar, id value):设置成员变量的值(基本类型需用(__bridge id)(void *)进行转换,否则编译错误)
      • id object_getIvar(id obj, Ivar ivar):获取成员变量的值
      • BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types):动态添加成员变量(已经注册的类是不能动态添加成员变量的)
      • const char *ivar_getName(Ivar v):获取成员变量的名字(需转成NSString才是真正的)
      • const char *ivar_getTypeEncoding(Ivar v):获取成员变量的类型信息
    • 属性
      • objc_property_t class_getProperty(Class cls, const char *name):获取一个属性
      • objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount):拷贝属性列表(最后需要调用free释放)
      • BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount):动态添加属性
      • void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount):动态替换属性
      • const char *property_getName(objc_property_t property):获取属性的名字(需转成NSString才是真正的)
      • const char *property_getAttributes(objc_property_t property):获取属性的类型信息
    • 方法
      • Method class_getInstanceMethod(Class cls, SEL name):获取对象方法
      • Method class_getClassMethod(Class cls, SEL name):获取类方法
      • IMP class_getMethodImplementation(Class cls, SEL name):获取方法的实现
      • IMP method_setImplementation(Method m, IMP imp):添加方法的实现
      • void method_exchangeImplementations(Method m1, Method m2):交换两个方法的实现
      • Method *class_copyMethodList(Class cls, unsigned int *outCount):拷贝方法列表(最后需要调用free释放)
      • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types):添加方法的实现
      • IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types):替换方法的实现
      • SEL method_getName(Method m):获取方法的名字
      • IMP method_getImplementation(Method m):获取方法的实现
      • const char *method_getTypeEncoding(Method m):获取方法的编码
      • unsigned int method_getNumberOfArguments(Method m):获取一个方法实际的参数个数
      • char *method_copyReturnType(Method m):获取一个方法的类型编码(需free释放)
      • char *method_copyArgumentType(Method m, unsigned int index):获取一个方法某个位置参数的类型编码(需free释放)
      • const char *sel_getName(SEL sel):获取方法的名字(需转成NSString才是真正的)
      • SEL sel_registerName(const char *str):字符注册一个方法名
      • IMP imp_implementationWithBlock(id block):用闭包封装一个方法的实现(不太常用)
      • id imp_getBlock(IMP anImp):获取一个实现的闭包(不常用)
      • BOOL imp_removeBlock(IMP anImp):移除一个实现的闭包(不常用)

    方法交换实际应用

    //方法事件点击:---- UIControl分类
    + (void)load
    {
        // hook:钩子函数
        Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
        method_exchangeImplementations(method1, method2);
    }
    
    //拦截了所有点击的事件
    - (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
    {
        NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
        
        // 调用系统原来的实现
        [self mj_sendAction:action to:target forEvent:event];
    }
    
    //拦击数组所有添加、插入的事件:---- NSMutableArray分类
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
            Class cls = NSClassFromString(@"__NSArrayM");
            Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
            Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
            method_exchangeImplementations(method1, method2);
        });
    }
    
    - (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index {
        if (anObject == nil) return;
        
        [self mj_insertObject:anObject atIndex:index];
    }
    
    //拦击字典所有赋值、取值的事件:---- NSMutableDictionary分类
    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
             //赋值NSMutableDictionary真正基类是__NSDictionaryM
            Class cls = NSClassFromString(@"__NSDictionaryM");
            Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
            Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
            method_exchangeImplementations(method1, method2);
            
            //获值时的真正基类是__NSDictionaryI
            Class cls2 = NSClassFromString(@"__NSDictionaryI");
            Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
            Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
            method_exchangeImplementations(method3, method4);
        });
    }
    
    - (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
        if (!key) return;
        
        [self mj_setObject:obj forKeyedSubscript:key];
    }
    
    - (id)mj_objectForKeyedSubscript:(id)key {
        if (!key) return nil;
        
        return [self mj_objectForKeyedSubscript:key];
    }
    

    面试题

    1、讲一下OC的消息机制?

    (详情见上)OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
    objc_msgSend底层有3大阶段
    消息发送(当前类、父类中查找)、动态方法解析、消息转发

    2、什么是Runtime?平时项目中有用过么?

    解释:
    OC是一个门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行,
    OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性的函数,
    平时编写的OC代码,底层都是转换成了Runtime API进行的。

    具体应用:
    利用关联对象(AssociatedObject)给分类添加属性
    变量类的所有成员变量(修复textfield的占位文字颜色、字典转模型、自动归档和解档)
    交换方法的实现(交换系统的方法,如:重写reload方法实现空白占位图自动消失和出现)
    利用消息转发机制解决方法找不到的异常问题。(防Crash处理)
    ....

    相关文章

      网友评论

          本文标题:底层探索--runtime的本质

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