OC super关键字,isMemberOfClass,isKindOfClass区别

今天研究一下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,这又是为什么呢?
这就是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,isKindOfClass区别)