iOS底层-Runtime系列三

super的本质

  1. 实例:

     #import "Student.h"
     @implementation Student
        
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             NSLog(@"%@",[self class]);//Student
             NSLog(@"%@",[self superclass]);//Person
             NSLog(@"----------");
             NSLog(@"%@",[super class]);//Student
             NSLog(@"%@",[super superclass]);//Person
         }
         return self;
     }
     @end
    
    1. 为何[super class][super superclass]返回的是Student与Person,难道不该是Person与Nsobject吗?
  2. 分析super的底层:将下面代码编译成C++,核心代码如下

    1. OC代码

       #import "Student.h"
       @implementation Student
              
       -(void)run{
           [super run];
       }
       @end
      
    2. C++核心代码如下

       //super结构体
       struct __rw_objc_super {
       struct objc_object *object;
       struct objc_object *superClass;
       __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
       };
               
        //run函数
       static void _I_Student_run(Student * self, SEL _cmd) {
        //消息发送
       ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
       }
              
      
      1. 简化

          //简化
         struct __rw_objc_super arg = {
         self,
         class_getSuperclass(objc_getClass("Student")) //本质就是[Person class];
         };
                     
         //objc_msgSendSuper(arg, sel_registerName("run"));
                     
         objc_msgSendSuper(arg, @selector(run));
        
  3. objc公开源码分析:
    1. 找到objc_super结构体对象的定义

       /// Specifies the superclass of an instance.
       struct objc_super {
           /// Specifies an instance of a class.
           //一个类的实例
           __unsafe_unretained _Nonnull id receiver;
           /// Specifies the particular superclass of the instance to message.
           //一个类实例的父类,而且仅仅是第一个父类
           __unsafe_unretained _Nonnull Class super_class;   //哪里去搜索方法的实现
           /* super_class is the first class to search */
       };
      
  4. 分析:
    1. [super class],本质是调用这个函数:

       OBJC_EXPORT void
      objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
      
    2. 第一个参数是objc_super结构体对象

       {
           self,
           [Person class]
       }
      
    3. [super class]本质是:

       struct objc_super arg = {
           self,
           [Person class]
       };
       objc_msgSendSuper(arg, @selector(run));
      
  5. 最开始的实例本质如下:

     - (instancetype)init
     {
         self = [super init];
         if (self) {
             NSLog(@"%@",[self class]);//Student
             NSLog(@"%@",[self superclass]);//Person
             NSLog(@"----------");
             //本质:objc_msgSendSuper({self,[Student class]},@selector(class));
             NSLog(@"%@",[super class]);//Student
             //本质:objc_msgSendSuper({self,[Student superclass]},@selector(class));
             NSLog(@"%@",[super superclass]);//Person
         }
         return self;
     }
    
  6. 总结
    1. [super message]的底层实现
      1. 消息接收者receiver仍然是子类对象
      2. 从父类开始查找方法
  7. 注意:class与superclass方法
    1. 都是在NSObject.mm中实现的
    2. 在开源objec中的NSObject.mm可以查到:

       //class的底层实现
       //因此class这个方法的返回值取决于self,即receiver
       -(Class)class{
           return  object_getClass(self);
       }
       //superclass的底层实现
       -(Class)superclass{
           return  class_getSuperclass(object_getClass(self));
       }
      
  8. 汇编本质(以这个为准)
    1. super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
      1. struct objc_super2
      2. SEL
    2. 与编译成C++区别,名称不一样(objc_msgSendSuper2、objc_super2)

isMemberOfClass、isKindOfClass

  1. 这两个方法在objc开源的NSObject.mm中已经开源,如下:

     //object_getClass这个函数如果参数cls是类对象那么返回的是元类对象了
     + (BOOL)isMemberOfClass:(Class)cls {
     return object_getClass((id)self) == cls;
     }
         
     - (BOOL)isMemberOfClass:(Class)cls {
     return [self class] == cls;
     }
         
     //object_getClass这个函数如果参数cls是类对象那么返回的是元类对象了
     + (BOOL)isKindOfClass:(Class)cls {
     for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
     if (tcls == cls) return YES;
     }
     return NO;
     }
         
     - (BOOL)isKindOfClass:(Class)cls {
     for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
     if (tcls == cls) return YES;
     }
     return NO;
     }
    
    1. 对象方法:

       - isMemberOfClass :左边的对象,是否是右边的类型
       - isKindOfClass :边的对象,对否是右边的类型,或者是他的子类型 
      
    2. 类方法

       + isMemberOfClass :左边类的元类,是否是右边的类型
       + isKindOfClass : 左边类的元类,对否是右边的类型,或者是他的子类型
      
  2. 示例:

     /*********对象方法**********/
     //- isMemberOfClass :左边的对象,是否是右边的类型
     // - isKindOfClass : 左边的对象,对否是右边的类型,或者是他的子类型
     id person = [[Person alloc] init];
     NSLog(@"====%d",[person isMemberOfClass:[Person class]]);  //1
     NSLog(@"====%d",[person isMemberOfClass:[NSObject class]]); //0
        
     NSLog(@"====%d",[person isKindOfClass:[Person class]]);  //1
     NSLog(@"====%d",[person isKindOfClass:[NSObject class]]); //1
        
        
     /*********类方法**********/
     //+ isMemberOfClass :左边类的元类,是否是右边的类型
     // + isKindOfClass : 左边类的元类,对否是右边的类型,或者是他的子类型
     NSLog(@"====%d",[Person isMemberOfClass:[Person class]]);  //0
     //左边传类,右边传元类才是正确的比较
     NSLog(@"====%d",[Person isMemberOfClass:object_getClass([Person class])]);  //1
     NSLog(@"====%d",[Person isMemberOfClass:[NSObject class]]); //0
        
        
        
     NSLog(@"====%d",[Person isKindOfClass:[Person class]]);  //0
     //NSObject的元类等于它本身
     NSLog(@"====%d",[Person isKindOfClass:[NSObject class]]); //1
    
  3. 总结:

    1. 区别:
      1. isMemberOfClass:判断两个类是否正好相等
      2. isKindOfClass:判断2个类是否是继承体系
    2. 如何使用:(两个方法同样适用)
      1. 如果是对象方法:左边一定要是实例对象,右边参数传类对象
      2. 如果是类方法:左边一定是类,右边参数传元类对象

LLVM的中间代码(IR)

  1. Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)(注意了,可不是转成C++代码)
    1. OC->llvm转成的中间代码->汇编->机器码
    2. C++仅仅是OC底层方法的实现
  2. 可以使用以下命令行指令生成中间代码(.ll文件)

     clang -emit-llvm -S main.m
    
  3. .ll文件内部是一种全新的语法

     @ - 全局变量
     % - 局部变量
     alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
     i32 - 32位4字节的整数
     align - 对齐
     load - 读出,store 写入
     icmp - 两个整数值比较,返回布尔值
     br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
     label - 代码标签
     call - 调用函数
    
  4. 具体可以参考官方文档:https://llvm.org/docs/LangRef.html

runtime的API

  1. 相关API如下:

     动态创建一个类(参数:父类,类名,额外的内存空间)
     Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
        
     注册一个类(要在类注册之前添加成员变量)
     void objc_registerClassPair(Class cls)
        
     销毁一个类
     void objc_disposeClassPair(Class cls)
        
     获取isa指向的Class
     Class object_getClass(id obj)
        
     设置isa指向的Class
     Class object_setClass(id obj, Class cls)
        
     判断一个OC对象是否为Class
     BOOL object_isClass(id obj)
        
     判断一个Class是否为元类
     BOOL class_isMetaClass(Class cls)
        
     获取父类
     Class class_getSuperclass(Class cls)
    
  2. 示例代码:

     // object_getClass:获取对象isa指向的Class,参数可以是实例对象(获取的为类对象),类对象(获取的为元类对象)
     void test1(){
         Person *person = [[Person alloc] init];
         [person run];
         //0x100001190====0x100001190
         NSLog(@"%p====%p",object_getClass(person),[Person class]);
         //元类地址: ====0x100001188
         NSLog(@"====%p",object_getClass([Person class]));
     }
     //object_setClass:  设置对象的isa指向的Class,参数可以是实例对象(设置的是类对象),类对象(设置的元类对象)
     void test2(){
         Person *person = [[Person alloc] init];
         //====person run=====
         [person run];
         object_setClass(person, [Car class]);
         //打印: ====Car
         NSLog(@"====%@",[person class]);
         //打印:====Car run=====
         [person run];
     }
        
     void test3(){
         Person *person = [[Person alloc] init];
         //====person run=====
         [person run];
         ;
         //object_isClass:判断一个对象是否为一个类对象或者元类对象
         //打印:0====1===1 ,元类对象也是一种特殊的类对象
         NSLog(@"%d====%d===%d", object_isClass(person),object_isClass([Person class]),object_isClass(object_getClass([Person class])));
         //class_isMetaClass: 判断一个Class是否为元类
         //打印: 0====1
         NSLog(@"%d====%d",class_isMetaClass([Person class]),class_isMetaClass(object_getClass([Person class])));
         //class_getSuperclass: 获取父类
         //NSObject
         NSLog(@"====%@",class_getSuperclass([person class]));
     }
        
     void run(id self,SEL _cmd){
         NSLog(@"====%@===%@==",self,NSStringFromSelector(_cmd));
     }
        
        
     void test4(){
         //动态创建一个类(参数:父类,类名,额外的内存空间)
         Class NewCls =  objc_allocateClassPair([NSObject class], "Dog", 0);
         //给一个类添加一个成员变量
         class_addIvar(NewCls, "_age", 4, 1, @encode(int));
         class_addIvar(NewCls, "_weight", 4, 1, @encode(int));
         //给一个类添加一个方法
         class_addMethod(NewCls, @selector(run),(IMP)run, "v@:");
         //注册一个类(要在类注册之前添加成员变量)
         objc_registerClassPair(NewCls);
         id dog = [[NewCls alloc] init];
            
         //==16=======
         NSLog(@"==%zd=======",class_getInstanceSize(NewCls));
         [dog setValue:@(6) forKey:@"_age"];
         [dog setValue:@(50) forKey:@"_weight"];
         //==6===50====
         NSLog(@"==%@===%@====",[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);
         //====<Dog: 0x102011160>===run==
         [dog run];
         //销毁一个类objc_disposeClassPair
         objc_disposeClassPair(NewCls);
     }
    

成员变量

  1. 相关API如下:

     获取一个实例变量信息
     Ivar class_getInstanceVariable(Class cls, const char *name)
        
     拷贝实例变量列表(最后需要调用free释放)
     Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
        
     设置和获取成员变量的值
     void object_setIvar(id obj, Ivar ivar, id value)
     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)
     const char *ivar_getTypeEncoding(Ivar v)
    
  2. 示例:

     //成员变量相关
     void test5(){
         //获取一个成员变量信息
         Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
         //打印: _age===i
         NSLog(@"%s===%s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));
            
         Person *person = [[Person alloc] init];
         //设置实例成员变量的值
         object_setIvar(person, ageIvar, @(20));
         //获取实例成员变量的值
         NSLog(@"====%@",object_getIvar(person, ageIvar));
         NSLog(@"====%d",person.age);
            
         //class_copyIvarList: 拷贝实例变量列表(最后需要调用free释放)
         unsigned int count = 0;
         //数组指针
         Ivar * ivarList = class_copyIvarList([Person class], &count);
         //遍历所有的成员变量
         for (int i =0; i<count; i++) {
             Ivar ivar = ivarList[i];
             NSLog(@"%s===%s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
         }
         //runtime中如果用copy或者create创建出来的都要用free释放掉
         free(ivarList);
     }
    

属性

  1. 相关API如下:

     获取一个属性
     objc_property_t class_getProperty(Class cls, const char *name)
        
     拷贝属性列表(最后需要调用free释放)
     objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
        
     动态添加属性
     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)
     const char *property_getAttributes(objc_property_t property)
    

方法

  1. 相关API如下:

     获得一个实例方法、类方法
     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)
        
     拷贝方法列表(最后需要调用free释放)
     Method *class_copyMethodList(Class cls, unsigned int *outCount)
        
     动态添加方法
     BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
        
     动态替换方法
     IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
        
     获取方法的相关信息(带有copy的需要调用free去释放)
     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)
     char *method_copyArgumentType(Method m, unsigned int index)
        
     选择器相关
     const char *sel_getName(SEL sel)
     SEL sel_registerName(const char *str)
        
     用block作为方法实现
     IMP imp_implementationWithBlock(id block)
     id imp_getBlock(IMP anImp)
     BOOL imp_removeBlock(IMP anImp)
    
  2. 示例:

     void myrun(){
         NSLog(@"====myrun=====");
     }
        
     void test6(){
         Person *person = [[Person alloc] init];
         //方法替换
         //        class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
         class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
             NSLog(@"====block");
         }),"v");
         [person run];
            
         //方法交换
         Method runimp =   class_getInstanceMethod([Person class], @selector(run));
         Method testimp =   class_getInstanceMethod([Person class], @selector(test));
         method_exchangeImplementations(runimp, testimp);
         //====person test=====
         [person run];
     }
    

黑魔法的使用

  1. method_exchangeImplementations俗称黑魔法,使用场合很多
    1. 监听整个项目的按钮点击事件
    2. 监听可变数组添加nil值(防止崩溃)
    3. 监听可变字典key值为nil(峰值崩溃)
    4. 等等
  2. 代码举例:

     //NSObject+ZHExtension.m
     #import <UIKit/UIKit.h>
     #import <objc/runtime.h>
     /**
      UIControl的分类,用于拦截所有的点击事件
      */
     @implementation UIControl (Extension)
     +(void)load{
         Method method1 = class_getInstanceMethod([self  class], @selector(sendAction:to:forEvent:));
         Method method2 = class_getInstanceMethod([self class], @selector(zh_sendAction:to:forEvent:));
         method_exchangeImplementations(method1, method2);
     }
     /**
      这个方法是UIControl的,一点点击事件触发,优先调用这个方法,然后才会去调用action方法
      */
     //-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
     //    NSLog(@"=============");
     //}
        
     -(void)zh_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
           
         [self zh_sendAction:action to:target forEvent:event];
         if ([self isKindOfClass:[UIButton class]]) {
             //这里监听所有按钮的点击
              NSLog(@"======按钮点击=======");
         }
     }
     @end
        
     /**
      UIViewController的分类,用于监听所有控制器的delloc情况
      */
        
     @implementation UIViewController (Extension)
        
     -(void)dealloc{
         NSLog(@"=======%@----delloc---",self.class);
     }
     @end
        
        
     /**
      监听NSMutableArray添加空值
      */
        
     @implementation NSMutableArray (Extension)
        
     //addObject:的本质还是调用这个方法
     //-(void)insertObject:(id)anObject atIndex:(NSUInteger)index{
     //
     //}
     +(void)load{
         //类簇:他们的真实类型是其他类型 NSString、 NSArray、NSDictionary
         //NSMutableArray的真实类型是:__NSArrayM
         Class cls = NSClassFromString(@"__NSArrayM");
         Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
         Method method2 = class_getInstanceMethod(cls, @selector(zh_insertObject:atIndex:));
         method_exchangeImplementations(method1, method2);
     }
        
     -(void)zh_insertObject:(id)anObject atIndex:(NSUInteger)index{
         if (!anObject) {
             NSLog(@"加入的数据为空值!!!!===%s====%d",__func__,__LINE__);
         }else{
             [self zh_insertObject:anObject atIndex:index];
         }
     }
        
     @end
        
     /**
      监听key值是否为nil
      dic[@"key"] = obj;
      底层会调用setObject:forKeyedSubscript:
      */
     @implementation NSMutableDictionary (Extension)
        
     +(void)load{
         //类簇:他们的真实类型是其他类型 NSString、 NSArray、NSDictionary
         //NSMutableArray的真实类型是:__NSArrayM
         Class cls = NSClassFromString(@"__NSDictionaryM");
         Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
         Method method2 = class_getInstanceMethod(cls, @selector(zh_setObject:forKeyedSubscript:));
         method_exchangeImplementations(method1, method2);
     }
        
     -(void)zh_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key{
         if (!key) {
             NSLog(@"==当前key值为空!!!==");
         }else{
             [self zh_setObject:obj forKeyedSubscript:key];
         }
     }
     @end
    
  3. 使用:

     @implementation ViewController
     - (IBAction)btnAction:(id)sender {
         NSLog(@"=====btn========");
     }
        
     - (IBAction)btnAction2:(id)sender {
         NSLog(@"=====btn2========");
     }
     - (IBAction)btnAction3:(id)sender {
         NSLog(@"=====btn3========");
     }
     - (void)btnAction4:(id)sender {
         NSLog(@"=====btn4========");
     }
        
     - (void)viewDidLoad {
         [super viewDidLoad];
         [self.BTN addTarget:self action:@selector(btnAction4:) forControlEvents:UIControlEventTouchUpInside];
            
         //监听可变数组添加是否为nil
         NSMutableArray *muarray = [NSMutableArray array];
         //__NSArrayM
         NSLog(@"%@",[muarray class]);
         [muarray addObject:nil];
            
         //可变字典key为nil
         id key = nil;
         NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
         dic[@"jack"] = nil;
         dic[key] = @"123";
     }
    

相关面试题

  1. 讲一下 OC 的消息机制
    1. OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
    2. objc_msgSend底层有3大阶段
      1. 消息发送(当前类、父类中查找)、动态方法解析、消息转发
  2. 什么是Runtime?平时项目中有用过么?
    1. OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
    2. OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
    3. 平时编写的OC代码,底层都是转换成了Runtime API进行调用
Table of Contents