OC消息机制和super关键字

消息发送

在Objective-C里面调用一个方法[object method],运行时会将它翻译成objc_msgSend(id self, SEL op, ...)的形式。

objc_msgSend

objc_msgSend的实现在objc-msg-arm.sobjc-msg-arm64.s等文件中,是通过汇编实现的。这里主要看在arm64objc-msg-arm64.s的实现。由于汇编不熟,里面的实现只能连看带猜。

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
	ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START

cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached

LNilOrTagged:
/* nil check,如果为空就是调用LReturnZero,LReturnZero里调用MESSENGER_END_NIL*/
b.eq LReturnZero // nil check

// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone

LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone

LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret

END_ENTRY _objc_msgSend

上面的流程可能是这样的:
objc_msgsend

CacheLookup的注释有两处:

  1. calls imp or objc_msgSend_uncached
  2. Locate the implementation for a selector in a class method cache.

即使看不懂汇编代码,但是从上面的注释我们可以猜测,消息机制会先从缓存中去查找。

__objc_msgSend_uncached

通过方法名我们可以知道,没有缓存的时候应该会执行__objc_msgSend_uncached

1
2
3
4
5
6
7
8
9
10
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search

MethodTableLookup
br x17

END_ENTRY __objc_msgSend_uncached

这里的MethodTableLookup里涉及到objc-runtime-new.mm文件中的_class_lookupMethodAndLoadCache3。该函数会调用lookUpImpOrForward函数。

lookUpImpOrForward

lookUpImpOrForward会返回一个imp,它的函数实现比较长,但是注释写的非常清楚。它的实现主要由以下几步(这里直接从缓存获取开始):

  1. 通过cache_getImp从缓存中获取方法,有则返回,否则进入第2步;
  2. 通过getMethodNoSuper_nolock从类的方法列表中获取,有加入缓存中并返回,否则进入第3步;
  3. 通过父类的缓存和父类的方法列表中寻找是否有对应的imp,此时会进入一个for循环,沿着类的父类一直往上找,直接找到NSObject为止。如果找到返回,否则进入第4步;
  4. 进入方法决议(method resolve)的过程即调用_class_resolveMethod,如果失败,进入第5步;
  5. 在缓存、当前类、父类以及方法决议都没有找到的情况下,Objective-C还为我们提供了最后一次翻身的机会,调用_objc_msgForward_impcache进行方法转发,如果找到便加入缓存;如果没有就crash。

上述过程中有几个比较重要的函数:

_class_resolveMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void _class_resolveMethod(Class cls, SEL sel, id inst) {
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}

上述函数会根据当前传入的类的是不是一个元类,在_class_resolveInstanceMethod_class_resolveClassMethod中选择一个进行调用。注释也说明了这两个方法的作用就是判断当前类是否实现了 resolveInstanceMethod:或者resolveClassMethod:方法,然后用objc_msgSend执行上述方法。

_class_resolveClassMethod

_class_resolveClassMethod_class_resolveInstanceMethod实现类似,这里就只看_class_resolveClassMethod的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) {
assert(cls->isMetaClass());

if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
//没有找到resolveClassMethod方法,直接返回。
return;
}

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);

// 缓存结果
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// 以下代码省略不影响阅读
}

_objc_msgForward_impcache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
STATIC_ENTRY __objc_msgForward_impcache

MESSENGER_START
nop
MESSENGER_END_SLOW

// No stret specialization.
b __objc_msgForward

END_ENTRY __objc_msgForward_impcache

ENTRY __objc_msgForward

adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17

END_ENTRY __objc_msgForward

_objc_msgForward_impcache用来进行消息转发,但是其真正的核心是调用_objc_msgForward

消息转发

关于_objc_msgForwardobjc中并没有其相关实现,只能看到_objc_forward_handler。其实_objc_msgForward的实现是在CFRuntime.c中的,但是开源出来的CFRuntime.c并没有相关实现,但是也不影响我们对真理的追求。

我们做几个实验来验证消息转发。

消息重定向测试

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
// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 验证消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(sendMessage)) {
return [BObject new];
}

return [super forwardingTargetForSelector:aSelector];
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];

运行结果:

1
2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message

forwardingTargetForSelector:处打个断点,查看一下调用栈:
message_redirection

_CF_forwarding_prep_0___forwarding___这两个方法会先被调用了,之后调用了forwardingTargetForSelector:

方法签名测试

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
// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;
}

/** 方法签名测试 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(sendMessage)) {
return [BObject instanceMethodSignatureForSelector:@selector(sendMessage)];
}

return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if (selector == @selector(sendMessage)) {
[anInvocation invokeWithTarget:[BObject new]];
} else {
[super forwardInvocation:anInvocation];
}
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];

method_signature

代码执行结果和消息重定向测试的运行结果一致。_CF_forwarding_prep_0___forwarding___这两个方法又再次被调用了,之后代码会先执行forwardingTargetForSelector:(消息重定向),消息重定向如果失败后调用methodSignatureForSelector:forwardInvocation:方法签名。所以说___forwarding___方法才是消息转发的真正实现。

crash测试

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
// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
}

/** 验证Crash */
- (void)doesNotRecognizeSelector:(SEL)aSelector {
if (aSelector == @selector(sendMessage)) {
NSLog(@"%@ doesNotRecognizeSelector", self.class);
}
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];

代码运行结果肯定是crash,结合上面的代码我们知道消息转发会调用___forwarding___这个内部方法。___forwarding___方法调用顺序是forwardingTargetForSelector:->methodSignatureForSelector:->doesNotRecognizeSelector:

我们用一张图表示整个消息发送的过程:
消息机制流程图

super关键字

我们先查看一下执行[super init]的时候,调用了那些方法
super_init

objc_msgSendSuper2的声明在objc-abi.h

1
2
3
4
// objc_msgSendSuper2() takes the current search class, not its superclass.
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);

objc_super的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;

/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};

从上面的定义我们可以知道receiver即消息的实际接收者,
super_class为指向当前类的父类。

所以该函数实际的操作是:从objc_super结构体指向的super_class开始查找,直到会找到NSObject的方法为止。找到后以receiver去调用。当然整个查找的过程还是和消息发送的流程一样。

所以我们能理解为什么下面这段代码执行的结果都是AObject了吧。虽然使用[super class],但是真正执行方法的对象还是AObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 代码
@implementation AObject

- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@", [super class]);
NSLog(@"%@", [self class]);
}

return self;
}

@end

// 执行结果
2019-03-12 19:44:46.003313+0800 iOSCodeLearning[34431:7234182] AObject
2019-03-12 19:44:46.003442+0800 iOSCodeLearning[34431:7234182] AObject