Words Of Life

isKindOfClass and isMemberOfClass

  • 摘要:isKindOfClass、isMemberOfClass 之思


前言

最近在学习 Objc Runtime 相关知识时,思考了一下 Sunnyxx 提出的 “入院考试”,下文主要对 isKindOfClass、isMemberOfClass 的知识点进行整理。

正文

- isKind/MemebrOfClass

我们先来看一下”入院考试”的题目,毕竟要先有需求,才能解决需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@”%d %d %d %d”, res1, res2, res3, res4);
// 输出的结果应该为什么?
}
return 0;
}

也许部分开发者看到这已经准备关闭这篇博客,”不就是调用一下,然后输出真假嘛,有什么难的呢?”

稍安勿躁,稍安勿躁~相信大家都知道这两个方法一般情况下的调用情况

  • isKindOfClass:判断实例类是否属于传入类的同种类(即当前传入类或当前传入的类的父类或父父类…)
  • isMemberOfClass:判断实例类是否等于传入类

因此,一般情况下,这两个方法会像这样执行

1
2
3
// Son and Father are both classes, Son extends Father
BOOL resI = [Son isKindOfClass:[Father class]]; // output: YES
BOOL resII = [Son isMemberOfClass:[Father class]]; // output: NO

Method Implementation

这两个方法内部是怎么实现的呢?代码之下无秘密,我们来看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// objc-class.mm #line 191:获得 isa 指针,进而判断类型。
// 若 obj 为类,最终获得的为元类类型 (metaClass);若 obj 为实例,则获得类类型 (class)
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
// NSObject.mm #line 1976:获得 class 类型
+ (Class)class {
return self;
}
// NSObject.mm #line 1980:通过 object_getClass 获得 class 类型
- (Class)class {
return object_getClass(self);
}
// NSObject.mm #line 1992:通过 object_getClass 获得 metaClass 类型,并与传入类判断
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
// NSObject.mm #line 1996:通过 object_getClass 获得 class 类型,并与传入类判断
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
// NSObject.mm #line 2000:通过 object_getClass 获得 metaClass 类型,并与传入类进行循环判断
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
// NSObject.mm #line 2007:通过 object_getClass 获得 class 类型,并与传入类进行循环判断
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

+ isKind/MemebrOfClass

接下来我们看看”入院考试”中,消息的发送者,它并不是一个对象,而是一个类。这会发生什么情况呢?我们先来看看”入院考试”的执行结果

1
2
3
4
5
6
7
8
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
// output: YES
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
// output: NO
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
// output: NO
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
// output: NO

相信如果理解了上述源码的朋友看到这,应该理解了为什么结果会是这样

至于还没有理解的朋友,不用心急,我们下面继续分析

在分析之前,我们首先来看一张关于 isa 指针的传世之图(很抱歉,没找到原图的出处链接)

isa

这里需要注意的是,每个实例会有一个 isa 指针指向它的类,每个类也有一个 isa 指针指向类的类(即元类)。根元类的 isa 指针指向自己,superclass 指针则指向 NSObject(Root Class)

接着,让我们来继续通过注释分析一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Son and Father are both classes, Son extends Father
int main(int argc, const char * argv[]) {
@autoreleasepool {
Father *f = [[Father alloc] init];
// 新创建一个 Son 实例
Son *s = [[Son alloc]] init];
// Instance Method - 将 tcls 与传入值比较,不等则 tcls = tcls->superclass
BOOL res0 = [s isKindOfClass:[s class]];
// 获取 tcls: tcls = object_getClass(s), tcls = Son(class)
// 第一次比较: Son(class) == Son(class) ? YES(Final Choose) : Continue
NSLog(@"%d, res0);
// output YES
BOOL res1 = [s isKindOfClass:[f class]];
// 获取 tcls: tcls = object_getClass(s), tcls = Son(class)
// 第一次比较: Son(class) == Father(class) ? YES : Continue
// 第二次比较: Father(class) == Father(class) ? YES(Final Choose) : Continue
NSLog(@"%d, res1);
// output YES
// =============================================================
// Class Method - 将 tcls 与传入值比较,不等则 tcls = tcls->superclass
BOOL resA = [(id)[s class] isKindOfClass:[s class]];
// 获取 tcls: tcls = object_getClass(s), tcls = Son(metaClass)
// 第一次比较: Son(metaClass) == Son(class) ? YES : Continue
// 第二次比较: Father(metaClass) == Son(class) ? YES : Continue
// 第三次比较: NSObject(metaClass) == Son(class) ? YES : Continue
// 第四次比较: NSObject(class) == Son(class) ? YES : Continue
// 第五次比较: Nil == Son(class) ? YES : NO(Final Choose)
NSLog(@"%d, resA);
// output NO
BOOL resB = [(id)[s class] isKindOfClass:[Son class]];
// 获取 tcls: tcls = object_getClass(s), tcls = Son(metaClass)
// 第一次比较: Son(metaClass) == Son(class) ? YES : Continue
// 第二次比较: Father(metaClass) == Son(class) ? YES : Continue
// 第三次比较: NSObject(metaClass) == Son(class) ? YES : Continue
// 第四次比较: NSObject(class) == Son(class) ? YES : Continue
// 第五次比较: Nil == Son(class) ? YES : NO(Final Choose)
NSLog(@"%d, resB);
// Output NO
BOOL resC = [(id)[s class] isKindOfClass:[f class]];
// 获取 tcls: tcls = object_getClass(s), tcls = Son(metaClass)
// 第一次比较: Son(metaClass) == Father(class) ? YES : Continue
// 第二次比较: Father(metaClass) == Father(class) ? YES : Continue
// 第三次比较: NSObject(metaClass) == Father(class) ? YES : Continue
// 第四次比较: NSObject(class) == Father(class) ? YES : Continue
// 第五次比较: Nil == Father(class) ? YES : NO(Final Choose)
NSLog(@"%d, resC);
// output NO
BOOL resD = [(id)[NSObject class] isKindOfClass:[NSObject class]];
// 获取 tcls: tcls = object_getClass(s), tcls = NSObject(metaClass)
// 第一次比较: NSObject(metaClass) == NSObject(class) ? YES : Continue
// 第二次比较: NSObject(class) == NSObject(class) ? YES(Final Choose) : Continue
NSLog(@"%d, resD);
// output YES
// Instance Method - 将 tcls 与传入值比较,不等则结束
BOOL res3 = [s isMemberOfClass:[s class]];
// 获取 tcls: tcls = Son(class)
// 进行一次比较: Son(class) == Son(class) ? YES(Final Choose) : NO
NSLog(@"%d, res3);
// output YES
BOOL res4 = [s isMemberOfClass:[Father class]];
// 获取 tcls: tcls = Son(class)
// 进行一次比较: Son(class) == Father(class) ? YES : NO(Final Choose)
NSLog(@"%d, res4);
// output NO
// =============================================================
// Class Method - 将 tcls 与传入值比较,不等则结束
BOOL resE = [(id)[s class] isMemberOfClass:[s class]];
// 获取 tcls: tcls = Son(metaClass)
// 进行一次比较: Son(metaClass) == Son(class) ? YES : NO(Final Choose)
NSLog(@"%d, resE);
// output NO
BOOL resF = [(id)[s class] isMemberOfClass:[Son class]];
// 获取 tcls: tcls = Son(metaClass)
// 进行一次比较: Son(metaClass) == Son(class) ? YES : NO(Final Choose)
NSLog(@"%d, resF);
// output NO
BOOL resG = [(id)[s class] isMemberOfClass:[f class]];
// 获取 tcls: tcls = Son(metaClass)
// 进行一次比较: Son(metaClass) == Father(class) ? YES : NO(Final Choose)
NSLog(@"%d, resG);
// output NO
BOOL resH = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
// 获取 tcls: tcls = Son(metaClass)
// 进行一次比较: NSObject(metaClass) == NSObject(class) ? YES : NO(Final Choose)
NSLog(@"%d, resH);
// output NO
}
}

从上述内容我们可以得到以下规律:

  • 对于 [A isKindOfClass:[B class]],只要 A 属于 B 的继承链,则为真
  • 对于 [(id)[A class] isKindOfClass:[B class]],仅当 B == NSObject 时为真,A 不影响结果
  • 对于 [A isMemberOfClass:[B class]],只要 A == B,即A 与 B 为同一类,则为真
  • 对于 [(id)[A class] isMemberOfClass:[B class]],无论 A 与 B 是否相等,必为假

  • 总结:

    本文分析了 isKindOfClass 与 isMemberOfClass 的不同点。需要注意的是,对于 object_getClass(obj) 这一方法:若 obj 为类,最终获得的为元类类型 (metaClass);若 obj 为实例,则获得类类型 (class)

  • 本文代码下载


  • Last Edited:2017.3.11
  • Author:@Seahub
  • Please contact me if you want to share this Article, 3Q~
五毛也是情, 一元也是爱