学习MJ的视频课程,整理总结知识点–class、super

isKindOfClass 和 isMemberOfClass

isKindOfClassisMemberOfClass的源码如下

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
- (Class)class {
return object_getClass(self);
}

+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == 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;
}

+ (BOOL)isSubclassOfClass:(Class)cls {
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

打印结果如下

1
2
3
4
5
6
7
8
9
id person = [[MJPerson alloc] init];

NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]); // 1
NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [person isKindOfClass:[MJPerson class]]); // 1
NSLog(@"%d", [person isKindOfClass:[NSObject class]]); // 1

NSLog(@"%d", [MJPerson isMemberOfClass:object_getClass([MJPerson class])]); // 1
NSLog(@"%d", [MJPerson isKindOfClass:object_getClass([NSObject class])]); // 1

super面试题

这里有一道面试题,如下,相应的DemoInterview02-super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end

@implementation MJPerson
- (void)print {
NSLog(@"my name is %@", self->_name);
}
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
@end

[(__bridge id)obj print]执行结果?

viewDidLoad函数内开始分析:

1
2
3
id cls = [MJPerson class]; // 栈中定义一个变量cls,cls指向MJPerson这个类
void *obj = &cls; // 栈中定义一个变量obj,obj指向cls的地址
[(__bridge id)obj print]; // 给变量obj发送print消息

我们先分析一个正常的调用场景的过程,如下

1
2
id person = [[MJPerson alloc] init];
[person print];

person指针指向MJPerson的一个实例对象,当调用print方法时,结合我们前面所学的Class的底层结构知道,会先通过person的isa指针拿到MJPerson这个类,在类中查找print方法并调用。
person指向的实例对象的底层结构是一个结构体,这个结构体的第一个成员是isa指针,也就意味着person指针指向的实例对象地址,同时也是实例对象的isa的地址,而实例对象isa的指向的地址是MJPerson这个类的地址,内部就会有类似如下的指针指向关系。
person ----> isa ----> MJPerson

我们再回过去分析上面的问题,它的指针指向关系如下:
obj ----> cls ----> MJPerson

两者虽然内容不一样,但指向关系相同。所以我们可以分析[(__bridge id)obj print];的流程,首先取obj指针指向的地址,为cls,等价于正常方法调用的流程的取isa指针;然后取isa指向的对象地址,正常流程是MJPerson,而cls指针指向的地址也是MJPerson,所以能正常调用print方法。

那么打印的self.name结果是什么呢

1
2
3
- (void)print {
NSLog(@"my name is %@", self.name);
}

上面我们可以分析出,[(__bridge id)obj print]这段代码可以正确的执行,我们知道,_name是MJPerson实例对象里面的一个属性,而实例对象结构体第一个成员是isa指针,接下来是_name,也就是print函数的self.name是isa地址后面8个字节的空间的内容。

obj是存储在栈空间的一个变量,栈空间是从高地址->低地址依次分配内存的,我们先从简单的分析入手,把代码改成如下这样:

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
[super viewDidLoad];

NSString *str = @"123";

id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}

此时打印的的结果是my name is 123,我们分析下此时栈空间的内存地址结构

低地址->高地址指针指向的内存区域
obj指针指向cls
cls指针指向MJPerson
str指针指向”123”

我们回到函数[(__bridge id)obj print]的调用流程进行分析,调用过程中会取obj的isa,并且把isa的地址增加_name的大小作为偏移量,也就是8字节。也就是会取obj指向的cls,并把cls的地址增加8个字节的偏移量作为_name的内存地址,此时便宜后刚好是str的内存区域。就解释了打印的结果是123

那么我们回到原本的问题,如下

1
2
3
4
5
6
7
- (void)viewDidLoad {
[super viewDidLoad];

id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}

此时cls的地址增加8字节是谁呢,这里有一个[super viewDidLoad];调用,我们之前的文章分析过,[super message];用clang重写会生成如下代码

1
2
struct objc_super arg = {self, [UIViewController class]};
objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));

这个过程会生成一个objc_super类型的结构体,结构体的第一个成员是self,也就是ViewController的实例对象。此时的函数内栈内存地址图如下

低地址->高地址指针指向的内存区域
obj指针指向cls
cls指针指向MJPerson
self指针指向ViewController实例对象
类指针指针指向[UIViewController class]

所以根据偏移量计算,_name的位置刚好是self对象,所以程序运行的结果如下

1
[(__bridge id)obj print]; // my name is <ViewController: 0x600002eade10>

super本质

对于[super message]这里有一些知识要补充。

[super message]的底层本质,我们上面也总结过,我们是根据源码导出的编译环境下的C++代码总结的,实际上并不能完全代表运行时的样子。

时机上运行时,真正调用的和编译时是不一样的,我们通过断点调试[super viewDidLoad]的执行过程,选择Xcode->Debug->Debug Workflow->Always Show Disassembly,来查看真实调用时转化的汇编代码,关键部分如下

1
2
3
0x10b457383 <+35>:  movq   0x2b9e(%rip), %rsi        ; "viewDidLoad"
0x10b45738a <+42>: leaq -0x20(%rbp), %rdi
0x10b45738e <+46>: callq 0x10b4578f4 ; symbol stub for: objc_msgSendSuper2

我们看到[super message]真实调用的底层函数是objc_msgSendSuper2,我们在苹果开源的objc4没有找到objc_msgSendSuper2的C实现,不过有汇编实现,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START

ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL

END_ENTRY _objc_msgSendSuper2

我们可以分析出objc_msgSendSuper2函数的第1个参数是receiver,第2个参数是class,但是它会通过class->superclass获取父类。我们编译阶段导出的源码第2个参数直接是superclass。两者还是有一些细微差别的。

验证_objc_msgSendSuper2的实现

上面关于_objc_msgSendSuper2的实现是我们根据汇编代码推出来的,实际上我们还可以通过运行时进行调试来验证我们推到的结论。

我们在[(__bridge id)obj print];添加断点,进行Debug,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
(lldb) p obj
(MJPerson *) $0 = 0x00007ffee94da4e8
(lldb) x/4g 0x00007ffee94da4e8
0x7ffee94da4e8: 0x0000000106725010 0x00007ff795704800
0x7ffee94da4f8: 0x0000000106724f48 0x00007fff516daaa8
(lldb) po 0x0000000106725010
MJPerson

(lldb) po 0x00007ff795704800
<ViewController: 0x7ff795704800>

(lldb) po 0x0000000106724f48
ViewController

我们发现obj紧挨着的下一个内存指向的是MJPerson,接下来指向的是ViewController的实例对象,再接下来指向的是ViewController的类。

如下表所示,验证了我们的猜想

低地址->高地址指针指向的内存区域
obj 0x00007ffee94da4e8指针指向cls
cls 0x0000000106725010指针指向MJPerson
self 0x00007ff795704800指针指向ViewController实例对象
类指针 0x0000000106724f48指针指向ViewController

总结

关于super面试题的分析,我们要知道这结构要点

  1. 熟悉OC的消息发送;拿到receiver,取receiver的isa,根据isa找class
  2. 熟悉实例对象的内存结构;isa后面跟着成员变量
  3. 栈空间是从高地址->低地址依次分配内存
  4. [super message]会先生生成一个objc_super的结构体,然后转化为objc_msgSendSuper(结构体, SEL)进行调用

super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
struct objc_super2
SEL

1
2
3
4
struct objc_super2 {
id receiver; // receiver是消息接收者
Class current_class; // current_class是receiver的Class对象
};

参考和源码

源码:
Interview02-super

评论