美文网首页
OC super关键字,isMemberOfClass,isKi

OC super关键字,isMemberOfClass,isKi

作者: 小心韩国人 | 来源:发表于2019-12-04 14:42 被阅读0次

    今天研究一下super关键字,在讲之前我们先看一下下面四条语句输出打印什么:备注说明:Student 继承 Person,两个类中都有 - (void)test 方法

    @implementation Student
    
    - (void)test{
        [super test];
        [self class];
        // class 方法的本质
        NSLog(@"[self class] : %@",[self class]); //取出 self 的类对象,所以应该输出 Student
        NSLog(@"[self superclass] : %@",[self superclass]);//取出 self 的 superclass ,应该输出  Peson
        NSLog(@"[super class] : %@",[super class]); //取出 self 的父类的类对象,应该输出 Person
        NSLog(@"[super superclass] : %@",[super superclass]);//取出 self 的父类的 superclass ,应该输出 NSObject
    }
    

    运行一下代码,看看我们分析的对不对:

    19-12-03 11:36:13.991206+0800 superTest[1494:254018] [self class] : Student
    2019-12-03 11:36:13.991651+0800 superTest[1494:254018] [self superclass] : Person
    2019-12-03 11:36:13.991691+0800 superTest[1494:254018] [super class] : Student
    2019-12-03 11:36:13.991734+0800 superTest[1494:254018] [super superclass] : Person
    

    [self class],[self superclass]我们都分析对了,为什么[super class],[super superclass]的结果和我们分析的不一样呢?
    要搞清楚这个问题我们就需要搞懂super关键字,class(),superClass()的底层,我们把Student.m转为c++代码看看底层是怎样的:

    super 底层
    发现super底层被转换为objc_msgSendSuper(arg1,arg2)函数,里面传入两个参数__rw_objc_super 结构体和 SEL,所以上面的代码也可以把__rw_objc_super抽离出去,换一种写法:
    __rw_objc_super 分离
    那么__rw_objc_super是什么呢,我们在runtime源码中搜搜objc_super:
    objc_super
    objc_super的底层就是消息的接受者和他的父类,结合这些底层知识我们把[super test]底层的c++代码修改一下:
    super etst
    super 方法的接受者仍然是子类,传入的父类是干嘛用的呢?
    我们在runtime源码中搜索objc_msgSendSuper:
    objc_msdSendSuper 注释
    原来 superclass 是为了告诉 runtime ,查找方法的时候直接从 superclass 的类中查找.❎不要在像以前那样通过 实例对象 isa 找到类对象,从类对象的方法列表中查找,如果找不到再通过类对象的 superclass 找到父类从父类的方法列表中查找.❎
    • class方法的底层:
    //class 底层实现
    - (Class)class{
        return object_getClass(self);//获取方法接受者的类对象或者元类对象
       // object_getClass底层调用的 getIsa(),如果是实例对象获取的就是类对象,如果是类对象获取的就是元类对象.
    }
    
    • superClass方法的底层:
    // superclass 底层实现
    - (Class)superclass{
        //先获取方法接受者的类对象
        //在获取它的父类对象
        return class_getSuperclass(object_getClass(self));
    }
    

    搞明白这几个关键字后,我们再回头看看[super class],[super superclass]:

    • [super class]:方法的接受者仍然是self,class()方法内部获取到self的类对象,所以还是Student.
    • [super superclass]:方法的接受者仍然是self,superclass()方法内部会现获取self的类对象Student,在获取Student的父类Person.
      其实我们再调用class()的时候,最终调用的都是NSObject类中的class()方法.

    isMemberOfClass 和 isKindOfClass 区别:

    我们看看下面四句输出语句,仔细想想会打印输出什么:

    - (void)test2{
        NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); 
        NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); 
        NSLog(@"%d", [Student isKindOfClass:[Student class]]); 
        NSLog(@"%d", [Student isMemberOfClass:[Student class]]); 
    }
    

    分析一下,感觉好像都是YES呀,实际运行一下看看结果:

    2019-12-03 15:31:24.838726+0800 superTest[1794:338777] 1
    2019-12-03 15:31:24.839133+0800 superTest[1794:338777] 0
    2019-12-03 15:31:24.839170+0800 superTest[1794:338777] 0
    2019-12-03 15:31:24.839224+0800 superTest[1794:338777] 0
    

    怎么样,跟你们分析的答案一样吗?我们还是看看这两个方法的本质:

    
    + (BOOL)isMemberOfClass:(Class)cls {
    
    // 获取接收者的元类对象 , 直接和 传进来的 cls 比较,也就是说传进来的 cls 也应该是 元类对象,否则就为 false
        return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
    // 获取消息接收者的类对象 和 传入的 cls 判断是否相等
        return [self class] == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
    
    // 第一步: 获取传入的消息接收者的元类对象
    // 第二步: 判断和传入的 cls 是否相等,也就是说传入的也必须是 元类对象, 否则为 false
    // 第三步: 如果不相等,继续找这个 元类对象的 superclass 继续比较,直到 superclass 为 nil.
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
    // 第一步: 取出消息接收者的类对象,和传入的 cls 判断是否相等
    // 第二步: 如果不相等,继续找这个类对象的 superclass 继续比较,直到 superclass 为nil 为止.
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    

    根据源码分析,+开头的方法,传入的必须是元类对象,所以我们后面三条输出语句都是false.如果想要输出true,这样改动即可:

    - (void)test2{
        NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
        NSLog(@"%d", [NSObject isMemberOfClass:object_getClass([NSObject class])]); //1
        NSLog(@"%d", [Student isKindOfClass:object_getClass([Student class])]); //1
        NSLog(@"%d", [Student isMemberOfClass:object_getClass([Student class])]); //1
    }
    // 输出结果
    2019-12-03 16:05:07.352199+0800 superTest[1820:351570] 1
    2019-12-03 16:05:07.352531+0800 superTest[1820:351570] 1
    2019-12-03 16:05:07.352564+0800 superTest[1820:351570] 1
    2019-12-03 16:05:07.352590+0800 superTest[1820:351570] 1
    

    那为什么第一条语句[NSObject isKindOfClass:[NSObject class]]同样也是输出true呢?右边应该是一个元类对象呀.我们在OC对象的底层结构及isa、superClass详解已经详细讲过isasuperclass.当时还重点把基类元类对象的 superclass 指向类对象这条线用⭕️标记了出来,因为它太特殊.

    基类元类对象的 superclass 指向类对象
    所以[NSObject isKindOfClass:[NSObject class]]这条语句会从元类对象一直找到NSObject类对象.事实上,所有继承自NSObject类的子类调用[** isKindOfClass:[NSObject class]]都是成立的.

    进阶训练:

    创建一个Person类,类中有一个有一个test方法:

    @interface Person : NSObject
    
    @property(nonatomic,copy)NSString *name;
    
    - (void)test;
    
    @end
    
    
    
    @implementation Person
    
    - (void)test{
        NSLog(@"my name is %@",self.name);
    }
    
    @end
    

    现在我们像下面这样调用:

    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSString *str = @"123";
        id personClass = [Person class];
        void * pointer = &personClass;
        [(__bridge Person *)pointer test];
    }
    
    @end
    
    

    大家分析一下pointer test能不能调用成功,为什么?如果调用成功会打印什么?为什么?
    我们运行一下代码,看看能不能成功:

    2019-12-04 09:56:44.802983+0800 指针调用方法[942:50150] my name is 123
    

    调用成功了,但是为什么打印的是123?这个123是不是局部变量 str = @"123"?我们修改str = @"666再运行一下:

    2019-12-04 09:59:01.033985+0800 指针调用方法[953:51910] my name is 666
    

    发现打印的就是局部变量str的值.很奇怪,怎么会这样呢?下面我们就好好分析一下:
    分析:
    如果我们要调用test方法,正常的做法应该是这样:

        //调用test方法正常步骤
        Person *person = [[Person alloc]init];
        [person test];
    

    我们在OC对象的底层结构及isa、superClass详解这一篇中知道了方法调用的本质就是通过isa指针找到对应的类,然后查找方法.所以上面的代码本质上就是这样:

    [person test] 本质
    我们再画图分析一下[(__bridge Person *)pointer test]:
    [(__bridge Person *)pointer test] 图解
    他们的内存结构何其相似.personClass在这里不就等价于isa么?他们都指向Person类对象.[person test]是通过实例对象 personisa指针找到Person 类对象,然后从类对象的方法列表中查找方法.所以,方法的调用本质就是只要能找到类对象.而[(__bridge Person *)pointer test],指针变量pointer中存储的personClass恰巧就指向类对象,所以最后能调用成功.
    那为什么打印my name is 666是局部变量的值呢?这是因为栈内存分配空间的机制导致的,我们写一个方法:
    - (void)stackMemoryTest{
        int a = 1;
        int b = 2;
        int c = 3;
        int d = 4;
        NSLog(@"a: %p",&a);
        NSLog(@"b: %p",&b);
        NSLog(@"c: %p",&c);
        NSLog(@"d: %p",&d);
    }
    // 打印结果
    2019-12-04 10:34:37.514299+0800 指针调用方法[1125:79517] a: 0x7ffeefbfea3c
    2019-12-04 10:34:37.514362+0800 指针调用方法[1125:79517] b: 0x7ffeefbfea38
    2019-12-04 10:34:37.514390+0800 指针调用方法[1125:79517] c: 0x7ffeefbfea34
    2019-12-04 10:34:37.514415+0800 指针调用方法[1125:79517] d: 0x7ffeefbfea30
    

    从打印结果中可以看到,栈内存分配空间是从高地址往低地址分配的,先创建的局部变量分配在高地址,后创建的分配在低地址.所以一下这段代码在内存中的布局如图:

    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSString *str = @"123";
        id personClass = [Person class];
        void * pointer = &personClass;
        [(__bridge Person *)pointer test];
    }
    
    @end
    
    
    viewdidLoad 内存布局
    那最后如何会输出 my name is 666呢?我们参考一下[ person test ]:
    [ person test ] 内存布局
    我们在获取self.name的时候,如果转换成汇编代码会发现本质上就是找到isa指针,然后越过8个字节,从而找到_name.这就是内存访问的本质:找到某块内存的地址,读取地址中的值.
    回到[(__bridge Person *)pointer test],pointer也会同样的越过personClass这8个字节找到str.所以最后就打印的是my name is 666;
    如果把NSString *str = @"666";这段代码注释掉会打印什么?
    2019-12-04 11:38:31.350106+0800 指针调用方法[1201:119557] my name is <ViewController: 0x600002913720>
    

    会发现打印的是ViewController,这又是为什么呢?
    这就是super关键字引起的.因为[super viewDidLoad];这句代码:

    [super viewDidLoad] 的底层
    super底层被转换为objc_msgSendSuper(arg1,arg2)这个函数,而这个函数需要两个参数.第一个参数就是__rw_objc_super结构体,这个结构体中有两个成员:self (ViewController) 和它的父类 UIViewController';第二个参数就是方法名了.
    所以,这里就相当于存在一个结构体类型的局部变量,我们画图说明:
    隐藏的局部结构体变量
    图中已经用红线标记出来了,在取self->name的时候,越过personClass的8个指针,整好找到了self也就是ViewController.从图中可以看到,只要比personClass先声明的局部变量,并且是先后声明的关系就会打印出来.
    比如,我们在添加一个str2:
    新增str2
    str2 内存布局

    关于 super 关键字的一点补充

    在前面讲super关键字的时候,我们看到super转换为c++代码的时候,被转换成了objc_msgSendSuper(arg1,arg2)函数.其实实际上底层执行并不是objc_msgSendSuper(arg1,arg2)函数,而是objc_msgSendSuper2(arg1,arg2)函数.我们在[super viewDidLoad];处打个断点,然后显示汇编语言看一下:

    super 底层调用
    并且上面讲的objc_msgSendSuper(arg1,arg2)中的第一个参数arg1__rw_objc_super结构体,这个结构体如下:
    struct __rw_objc_super { 
        struct objc_object *object; 
        struct objc_object *superClass; 
    }
    

    objc_super2的结构体如下:

    struct objc_super2 {
        id receiver;
        Class current_class;
    };
    

    可以看到objc_super2这个结构体中传入的是Class current_class;也就是当前类.而在_objc_msgSendSuper2内部获取当前类的superClass:

    _objc_msgSendSuper2 内部获取 superclass
    我们可以通过访问内存验证一下:

    如上图所示,我们取出结构体中的第二个成员看看是什么就清楚了.
    取出 结构体第二个参数内容
    所以,super底层其实是调用objc_msgSendSuper2()函数,然后传入的是当前类对象,只不过在内部又会取出当前类对象的superclass.
    这只是一个小细节,和我们最开头说的也不矛盾,知悉就好.

    相关文章

      网友评论

          本文标题:OC super关键字,isMemberOfClass,isKi

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