目前遗留的问题
- copy和strong修饰符的区别(objc_setProperty和内存平移, objc_getProperty都在什么情况下会调用)
- alloc的objc_alloc, objc_opt_class和objc_opt_isKindOfClass的符号查找
- 为什么第一次加载的时候firstSubclass=nil, 在执行或者调用了LGTeacher之后, 就会有firstSubclass=LGTeacher的赋值.
上一篇章遗留问题
- 动态方法决议走完之后, 源码中查询不到后续的流程了 , 是否就已经完毕了?
- 关于Class方法, 看源码逻辑先发送resolveClassMethod消息, 根据lookUpImpOrNilTryCache判断是否发送resolveInstanceMethod消息. 但是实际上是不会走resolveInstanceMethod方法的.
实际上测试了之后resolveInstanceMethod方法并不是不走了, 而是元类调用了resolveInstanceMethod找到了根元类, resolveInstanceMethod在NSObject里面实现了, 所以使用分类重写并打印resolveInstanceMethod. 是会出现打印的两次.
如果是实例方法找不到, 在本类重写了resolveInstanceMethod方法, 是不需要找到NSObject里面的. 因为在元类里面直接找得到, 不会在向根元类查询.
消息转发
事实上第一个问题的答案就是消息转发. 接下里我们新建一个项目:
@interface Person : NSObject
- (void)func1;
/**
* 未实现
*/
- (void)instanceF;
+ (void)classF;
@end
@implementation Person
- (void)func1 {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s %@", __func__, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, char * argv[]) {
Person *p = [Person alloc];
//未实现调用
[p instanceF];
return 0;
}
下面走了两次方法动态决议之后崩溃了. 这里我们看不出什么, 打个断点试一试:
整个堆栈信息如下:
可以看到第一次和第二次的堆栈调用:
-
_objc_msgSend_uncached
->resolveMethod_locked
->resolveInstanceMethod
触发了+[Person resolveInstanceMethod:]
第二次是
- CoreFoundation框架的
__forwarding_prep_0___
->___forwarding___
->-[NSObject(NSObject) methodSignatureForSelector:]
->__methodDescriptionForSelector
->libobjc下面的class_getInstanceMethod
->resolveMethod_locked
->resolveInstanceMethod
触发了+[Person resolveInstanceMethod:]
也就是说, 在我们平常开发中, 找不到方法实现的时候通常是会调用两次动态决议, 才会产生报错, 那么为什么会是这样, 我也不知道. 既然第二次是CF框架, 那么我们就看看CF里面有没有prep0看看是否能找到, 网址
我们把CF下载下来使用VSCode打开搜索, 发现不管是forwarding_prep_0_, prep, forwarding, methodSignatureForSelector通通搜不到. 所以可能是这部分没有开源.
反汇编CoreFoundation
找CoreFoundation
找到路径, 在文件夹中command+shift+G输入路径, 得到下图:
取出CoreFoundation可执行文件.
Hopper 反汇编CoreFoundation
hopper根据下图选择上面的可执行文件, 一路确定:
得到如下页面, 终于看到了我们想要的:
然后双击就可以进入对应的方法中:
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
rdi = arg0;
r15 = rdi;
var_30 = *___stack_chk_guard;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rax = rcx;
rbx = *(r15 + rax * 0x8);
rcx = *(r15 + rax * 0x8 + 0x8);
var_140 = rcx;
r13 = rax * 0x8;
if ((rbx & 0x1) == 0x0) goto loc_649bb;
loc_6498b:
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = rcx ^ rbx;
rax = rcx >> 0x1 & 0x7;
if (rax == 0x7) {
rcx = rcx >> 0x4;
rax = (rcx & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_64d48;
loc_649bb:
var_148 = r13;
var_138 = r12;
var_158 = rsi;
rax = object_getClass(rbx);
r12 = rax;
r13 = class_getName(rax);
r14 = @selector(forwardingTargetForSelector:);
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;
loc_649fc:
rdi = rbx;
rax = _objc_msgSend(rdi, r14);
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
loc_64a19:
r12 = var_138;
r13 = var_148;
if ((rax & 0x1) == 0x0) goto loc_64a5b;
loc_64a2b:
rdx = *_objc_debug_taggedpointer_obfuscator;
rdx = rdx ^ rax;
rcx = rdx >> 0x1 & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x4 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_64d45;
loc_64a5b:
*(0x0 + r13) = rax;
r15 = 0x0;
goto loc_64d82;
loc_64d82:
if (*___stack_chk_guard == var_30) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_64d45:
rbx = rax;
goto loc_64d48;
loc_64d48:
if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
loc_64d55:
rax = _getAtomTarget(rbx);
r14 = rax;
*(r15 + r13) = rax;
___invoking___(r12, r15);
if (*r15 == r14) {
*r15 = rbx;
}
goto loc_64d82;
loc_64ed1:
____forwarding___.cold.4();
rax = *(rdi + 0x8);
return rax;
loc_64a67:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
rbx = @selector(methodSignatureForSelector:);
r14 = var_138;
var_148 = r15;
if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;
loc_64ab2:
rax = _objc_msgSend(r14, rbx);
rbx = var_158;
if (rax == 0x0) goto loc_64e3c;
loc_64ad5:
r12 = rax;
rax = [rax _frameDescriptor];
r13 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
rax = sel_getName(var_140);
rdx = " not";
r8 = "";
rcx = r8;
if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = rdx;
}
if (rbx == 0x0) {
r8 = rdx;
}
rdx = rax;
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[2003]);
}
var_150 = r13;
if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_64c19;
loc_64b6c:
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
}
r15 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *____forwarding___.invClassSize;
rsp = rsp - ___chkstk_darwin();
r13 = rsp;
__bzero(r13, rsi);
rbx = rsp - ___chkstk_darwin();
objc_constructInstance(*____forwarding___.invClass, r13);
var_140 = r15;
[r13 _initWithMethodSignature:r12 frame:var_148 buffer:rbx size:r15];
[var_138 _forwardStackInvocation:r13];
r14 = 0x1;
goto loc_64c76;
loc_64c76:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *var_150;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rcx = *(int32_t *)(rax + 0x1c);
rdx = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
}
}
rax = [r12 methodReturnType];
rbx = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (r14 != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
[r13 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm{ fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (r14 != 0x0) {
[r13 release];
}
}
goto loc_64d82;
loc_64c19:
r15 = @selector(forwardInvocation:);
if (class_respondsToSelector(object_getClass(r14), r15) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
_objc_msgSend(0x0, r15);
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
loc_64e3c:
rax = sel_getName(var_140);
r14 = rax;
rax = sel_getUid(rax);
if (rax != var_140) {
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
}
rbx = @selector(doesNotRecognizeSelector:);
if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
____forwarding___.cold.2(var_138);
}
rax = _objc_msgSend(var_138, rbx);
asm{ ud2 };
return rax;
loc_64dd7:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
}
goto loc_64e3c;
loc_64dc1:
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_64dd7;
}
反汇编的代码很长, 直接看的话很懵逼, 我们一步一步挑重点还原一下, 细节不一定准确, 只是大致流程.
void ____forwarding___(Class cls, SEL sel) {
id receiver;
//loc_649bb
if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {
receiver = objc_msgSend(cls, @selector(forwardingTargetForSelector:));
}
//loc_649fc
if (!receiver) {
objc_msgSend(receiver, sel);
} else {
//loc_64a8a
if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {
//loc_64ab2:
objc_msgSend(cls, @selector(methodSignatureForSelector:));
//loc_64ad5:
if (class_respondsToSelector(cls, @selector(_forwardStackInvocation:))) { // 一般不实现, 先不管
//loc_64b6c:
objc_msgSend(cls, @selector(_forwardStackInvocation:));
} else {
//loc_64c19:
if (class_respondsToSelector(cls, @selector(forwardInvocation:))) {
rax = NSInvocation *
_objc_msgSend(0x0, @selector(forwardInvocation:));
//相当于执行了这个_objc_msgSend(rax, sel)
ret
}
}
} else {
goto loc_64e3c;
}
}
loc_64e3c
if (class_respondsToSelector(cls, @selector(doesNotRecognizeSelector:))) {
objc_msgSend(cls, @selector(doesNotRecognizeSelector:));
};
}
大概就是这么个流程, 不过在流程里我们看到了我们今天要探索的:
- 快速转发
forwardingTargetForSelector
- 慢速转发
methodSignatureForSelector
-forwardInvocation
快速转发
我们实现了方法
forwardingTargetForSelector
之后, 发现确实如prep_0一样,其实我们可以这样去验证反汇编, 就是我们打断点. 如下:
反汇编的寄存器的值, 在这里每个阶段都可以读取到, 然后再去配合着去读就会舒服很多.
我们用Proxy
类实现instanceF
方法, 然后在快速转发中返回会发生什么呢?
不会崩溃了, 而且第二的转发也不会走了, 问题解决了. 所以我们在第一层只需要返回一个id消息接受者, 接受者就会自动调用
objc_msdSend
然后继续进行消息处理.
从反汇编看到doesNotRecognizeSelector
所以我有点好奇, 我打印了一下如下:
也就是说, 写一个NSObject一个分类, 重写
doesNotRecognizeSelector
可以看到一次打印.我们下面继续实现一下慢速转发的两个方法.
慢速转发
methodSignatureForSelector
和forwardInvocation
是结合着使用的, 单独使用任何一个都没有用.
-
methodSignatureForSelector
返回方法签名 -
forwardInvocation
拿到方法签名, 设置接收者去实现, 或者不处理.
先看一下处理的效果:
11.png
神奇吧, 没有处理也没有崩溃.
但是看到图11的打印顺序会不会很奇怪, 第一次只走到了methodSignatureForSelector
就没有继续往下走了, 然后继续调用了resolveInstanceMethod
然后_forwardStackInvocation
, 最后forwardInvocation
.
也就是说, 消息转发的流程另外一种情况如下
- 1.实现了
methodSignatureForSelector
, 并且返回了对应的方法签名.- 2.实现了
forwardInvocation
方法,
在第二次进行转发的消息是_forwardStackInvocation
(如果_forwardStackInvocation
没有实现, 则需要forwardInvocation
去响应), 不然就报错.
我们在回到汇编中看一下:
大致就是这么个流程, 虽然画图技术拙劣但是已经尽量的表达清楚了.
在慢速转发中, 主要的点就是
-
methodSignatureForSelector
和forwardInvocation
一起搭配使用. -
methodSignatureForSelector
有正确返回值的准确情况下, 会走_forwardStackInvocation
或forwardInvocation
只需要实现方法即不会崩溃. -
methodSignatureForSelector
没有返回值的情况下,forwardInvocation
是无效的. 怎么处理都没有用.
继续看下面两张图:
图18和19, 只有一个区别就是一个是强引用了一个Proxy
的实例对象, 另外一个是局部变量, 在这个地方如果使用局部变量会被释放掉, 报出对象的野指针错误, 所以在这个地方调用的话, 要注意一点.
Thread 1: "-[Person instanceF]: unrecognized selector sent to instance 0x600000988160"
类的过程
类方法如下, 探究过程一样
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s %@", __func__, NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(classF)) {
NSMethodSignature *ms = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return ms;
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}
第二次流程
苹果显然是设计的消息流程, 给了两次转发的机会, 但是第二次的转发在原文最上面看到了这么一个调用methodDescriptionForSelector
, 我们并没有在汇编中看到, 那么它是在哪里的呢?
看图2是怎么从methodSignatureForSelector
到__methodDescriptionForSelector
. 做出如下改动:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
return nil;//返回值变为nil
}
动态决议走了一次, 消息转发到慢速阶段后面就没有了.
接下来我们看NSObject的定义:
这可咋办, 啥也没写, 我们现在只知道整个二次转发流程和[super methodSignatureForSelector]
肯定是有关系的.... 不要着急, 找一份objc的源码看看有没有什么新收获.源码
看见没, 这些都是CF的实际调用, 扩展给了objc使用. 现在感觉快看到希望了, 回到反汇编继续找CF.
void * -[NSObject methodSignatureForSelector:](void * self, void * _cmd, void * arg2) {
rdx = arg2;
rdi = self;
if ((rdx != 0x0) && (___methodDescriptionForSelector([rdi class], rdx) != 0x0)) {
rax = [NSMethodSignature signatureWithObjCTypes:rdx];
}
else {
rax = 0x0;
}
return rax;
}
int ___methodDescriptionForSelector(int arg0, int arg1) {
//省略部分代码
loc_7c68b:
rax = class_getInstanceMethod(var_40, rbx);
if (rax != 0x0) {
rax = method_getDescription(rax);
r15 = *rax;
rbx = *(rax + 0x8);
}
else {
rbx = 0x0;
r15 = 0x0;
}
goto loc_7c6b2;
}
function class_getInstanceMethod {
rax = _class_getInstanceMethod(rdi, rsi);
return rax;
}
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
[NSObjec methodSignatureForSelector]
>___methodDescriptionForSelector
->>class_getInstanceMethod
->objc
源码中->lookUpImpOrForward
回到了消息慢速查找流程.
整个闭环, 结束.
总结
消息转发的所有流程, 不管是快速转发还是慢速转发, 都跟__forwarding_prep_0___
有关, 第一次转发和第二次转发每一个环节变量的控制走与不走, 都需要自己进行大量的测试, 因为没有这些测试, 只是看这些枯燥的代码是记不住这些繁琐的流程的.
消息过程非常重要, 可以说每一个语言的消息流程设计都是核心中的核心, 有兴趣的小伙伴探索一下吧.
1