本文主要从源码层面梳理消息发送的整个流程,内容包括
1.消息发送,
2.动态方法解析,
3.消息转发
文中涉及的代码我会标记 行号 和 方法名,便于读者自行验证,我使用的runtime源码版本是objc4-779.1
,不同版本 行号可能出现差异.
1>文件名objc-msg-arms.s
,可以看到,它是由汇编编写的,这样运行效率更高, ENTRY
代表方法的入口,消息发送从ENTRY _objc_msgSend
这里开始执行,首先通过isa
指针拿到对象的类,然后再到方法缓存中查找对应的方法,对应的代码是GetClassFromIsa
和 CacheLookup NORMAL, _objc_msgSend
.
380:
ENTRY _objc_msgSend
cbz r0, LNilReceiver_f
ldr r9, [r0] // r9 = self->isa
GetClassFromIsa // r9 = class
CacheLookup NORMAL, _objc_msgSend
找到方法缓存的实现地址
bx r12 在这就直接调用方法了
CacheLookup2 NORMAL, _objc_msgSend
没有找到缓存的方法
ldr r9, [r0] // r9 = self->isa
GetClassFromIsa // r9 = class
b __objc_msgSend_uncached 既然没有找到方法,就去从类,父类,元类中查找
LNilReceiver:
// r0 is already zero
mov r1, #0
mov r2, #0
mov r3, #0
FP_RETURN_ZERO
bx lr
END_ENTRY _objc_msgSend
2>缓存查找的方法是,通过 SEL & mask来找到方法缓存的位置,如果有值就直接取出,对这个不太清楚的,可以看我的上一篇 传送门
244:
.macro CacheLookup
cache-miss
LLookupStart$1:
ldrh r12, [r9, #CACHE_MASK] // r12 = mask
ldr r9, [r9, #CACHE] // r9 = buckets
.if $0 == STRET
SEL & mask 获取方法imp的地址
and r12, r12, r2 // r12 = index = SEL & mask
.else
and r12, r12, r1 // r12 = index = SEL & mask
.endif
add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
ldr r12, [r9, #CACHED_SEL] // r12 = bucket->sel
6:
.if $0 == STRET
teq r12, r2
.else
teq r12, r1
.endif
bne 8f
ldr r12, [r9, #CACHED_IMP] // r12 = bucket->imp
.if $0 == STRET
tst r12, r12 // set ne for stret forwarding
.else
// eq already set for nonstret forwarding by `teq` above
.endif
.endmacro
3> 缓存中没有方法,_objc_msgSend_uncached
从这里继续跟进
710: STATIC_ENTRY __objc_msgSend_uncached
715: MethodTableLookup NORMAL
684: blx _lookUpImpOrForward 这里就到类中去查找方法了
4> 接下来就是核心代码 在文件objc-runtime-new.mm
5988:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
这里再到缓存中查找一遍,防止动态添加了方法
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
拿到当前类的方法列表去找sel,找到就跳到 done,
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
利用父类的指针做方法寻找
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
查找父类的缓存
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
父类中找到缓存,跳出,并缓存到自己的类中
goto done;
}
}
在缓存 父类中都没有找到方法,这个时候尝试动态方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
5> 动态方法解析
5928:
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
如果不是元类,调用这个方法[cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
如果是元类,调用这个方法[nonMetaClass resolveClassMethod:sel]
和 [cls resolveInstanceMethod:sel] 这个方法.
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
5.1 动态方法解析代码实践:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
sel:你需要动态解析的方法名
other: 你指派需要响应的方法,
返回值为YES 证明需要动态解析
if (sel == @selector(test)) {
Method method = class_getInstanceMethod(self, @selector(other));
class_addMethod(self,
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
NSLog(@"%s",__func__);
return [super resolveInstanceMethod:sel];
}
5.1.1 同样可以调用C语言函数
void c_other(id self ,SEL _cmd){
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
c_other :C语言函数名,
v16@0:8 函数编码
if (sel == @selector(test)) {
class_addMethod(self,
sel,
(IMP)c_other,
"v16@0:8");
return YES;
}
NSLog(@"%s",__func__);
return [super resolveInstanceMethod:sel];
}
6>源码中,我没有找到相关消息转发的代码,是不开源的,
这里贴出MJ老师的消息发送的流程图 image.png 动态方法解析 image.png7> 消息转发就在这四个方法里进行,这个是类方法的调用,对象方法把"+ " 换成 "-"即可
方法的调用顺序 由上至下
+ (BOOL)resolveClassMethod:(SEL)sel{
return NO; 是否需要动态解析方法,默认返回为NO
}
+ (id)forwardingTargetForSelector:(SEL)aSelector{
返回值为nil 说明要消息转发 ,会调用methodSignatureForSelector,默认返回值为nil
返回其他类:则调用其他类的 aSelector 方法,
注意:如果返回 对象,则调用对象方法,返回类,则调用类方法
// return nil;
return [[MJCat alloc]init] ;
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
判断方法名,返回方法签名
if(aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
这里可以指派其他类,去响应方法,如果anInvocation.selector 不指定,
则调用传过来的同名的方法,如果指定,则调用指定的方法.
anInvocation.selector = @selector(catTest);
[anInvocation invokeWithTarget:[MJCat class]];
}
如图:
image.png说明
1> 消息转发的疑问: 类方法里面如果返回的对象,即return [[MJCat alloc]init] ;
,会调对象方法,为什么类方法里面能够调用对象方法???
解答: 我们知道,对象方法可以访问外部属性,类方法不可以,本质来说就是,所有的对象方法,成员变量都放在类对象中,而类方法存放在元类中,而元类中只保存了类方法,所以我们平时开发中,类方法中不能调用对象方法,也不能访问外部变量,关于这点可以看我之前的文章 传送门,而消息转发是单纯的objc_msgSend([MJCat class], test)
,你给我类,我就去调用+(void)test,你给我objc_msgSend(cat, test)
, 我就调用-(void)test.
2> 消息转发可以做到指派其他的类响应同名的方法,也可以指派其他类的任意方法来响应该方法,比如我调用- [person instanceMethod]
,消息转发后,可以是- [cat intanceMethod
],-[cat instanceOtherMethod]
,+[MJCat classMethod]
.
小结:
1.以后说明问题最好还是用图片,或者举例,文字说明晦涩难懂,自己看都云里雾里~~~ 2. 所有简书涉及的知识点是我自己实践过的,简友们可放心食用,如果有不对的地方,欢迎留言.
小知识点记录
#define MJMask 1<<2 //0b0000 0100
~MJMask => 0b1111 1011//按位取反
方法还可以这样转换
struct method_t{
SEL sel;
char *types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
这两个是等价的
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
Method method1 = class_getInstanceMethod(self, @selector(other));
class_addMethod(self,
sel,
method->imp,
method->types);
return YES;
}
NSLog(@"%s",__func__);
return [super resolveInstanceMethod:sel];
}
网友评论