博客连接OC消息机制和super关键字缓存
在Objective-C里面调用一个方法[object method]
,运行时会将它翻译成objc_msgSend(id self, SEL op, ...)
的形式。bash
objc_msgSend
的实如今objc-msg-arm.s
、objc-msg-arm64.s
等文件中,是经过汇编实现的。这里主要看在arm64
即objc-msg-arm64.s
的实现。因为汇编不熟,里面的实现只能连看带猜。函数
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
复制代码
上面的流程多是这样的: 测试
从CacheLookup
的注释有两处:ui
calls imp or objc_msgSend_uncached
Locate the implementation for a selector in a class method cache.
即便看不懂汇编代码,可是从上面的注释咱们能够猜想,消息机制会先从缓存中去查找。spa
经过方法名咱们能够知道,没有缓存的时候应该会执行__objc_msgSend_uncached
。翻译
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
函数。debug
lookUpImpOrForward
会返回一个imp
,它的函数实现比较长,可是注释写的很是清楚。它的实现主要由如下几步(这里直接从缓存获取开始):code
cache_getImp
从缓存中获取方法,有则返回,不然进入第2步;getMethodNoSuper_nolock
从类的方法列表中获取,有加入缓存中并返回,不然进入第3步;for
循环,沿着类的父类一直往上找,直接找到NSObject为止。若是找到返回,不然进入第4步;_class_resolveMethod
,若是失败,进入第5步;_objc_msgForward_impcache
进行方法转发,若是找到便加入缓存;若是没有就crash。上述过程当中有几个比较重要的函数:cdn
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_resolveInstanceMethod
实现相似,这里就只看_class_resolveClassMethod
的实现。
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*/);
// 如下代码省略不影响阅读
}
复制代码
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_msgForward
在objc
中并无其相关实现,只能看到_objc_forward_handler
。其实_objc_msgForward
的实现是在CFRuntime.c
中的,可是开源出来的CFRuntime.c
并无相关实现,可是也不影响咱们对真理的追求。
咱们作几个实验来验证消息转发。
// .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];
复制代码
运行结果:
2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message
复制代码
在forwardingTargetForSelector:
处打个断点,查看一下调用栈:
_CF_forwarding_prep_0
和___forwarding___
这两个方法会先被调用了,以后调用了forwardingTargetForSelector:
。
// .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];
复制代码
代码执行结果和消息重定向测试的运行结果一致。_CF_forwarding_prep_0
和___forwarding___
这两个方法又再次被调用了,以后代码会先执行forwardingTargetForSelector:
(消息重定向),消息重定向若是失败后调用methodSignatureForSelector:
和forwardInvocation:
方法签名。因此说___forwarding___
方法才是消息转发的真正实现。
// .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 init]
的时候,调用了那些方法
objc_msgSendSuper2
的声明在objc-abi.h
中
// 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
的定义以下:
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
。
// 代码
@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
复制代码