底层探索--runtime的本质

基本

  • 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处理)
....

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容