OC中的方法调用,其实都是转换为objc_msgSend
函数的调用
objc_msgSend
执行流程大致分为三步:
- 消息发送
- 动态方法解析
- 消息转发
1.消息发送
首先创建命令行工程文件,创建MessageClass类
添加messageTest方法,并且在main中调用
#import <Foundation/Foundation.h>
#import "MessageClass.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MessageClass *message = [[MessageClass alloc] init];
[message messageTest];
}
return 0;
}
复制代码
将OC代码转换为C/C++代码:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
复制代码
这里将main.m文件转换为C++文件,并找到核心代码:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MessageClass *message = ((MessageClass *(*)(id, SEL))(void *)objc_msgSend)((id)((MessageClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MessageClass"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)message, sel_registerName("messageTest"));
}
return 0;
}
复制代码
可以直接看到转化后的方法调用代码:
((void (*)(id, SEL))(void *)objc_msgSend)((id)message, sel_registerName("messageTest"));
objc_msgSend((id)message, sel_registerName("messageTest")); // 简化强制转换的代码
复制代码
由此也印证文章开头的OC中的方法调用,其实都是转换为objc_msgSend函数的调用
现在我们下载runtime源码(下载最新版本源码),查看objc_msgSend
函数的相关实现,根据苹果注释我们粗略了解下实现源码:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// p0寄存器: 消息接受者 receiver
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS // 跳转到 NilOrTag判断.
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
// CacheLookup 缓存查找.
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged: // NilOrTag判断,判断是否为空.
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret // 可以理解为 return.
END_ENTRY _objc_msgSend
复制代码
查找方法:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;
methodListLock.assertUnlocked();
// Optimistic cache lookup
if (behavior & LOOKUP_CACHE) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto out_nolock;
}
// Check for freed class
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
// Check for +initialize
if ((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized()) {
initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
// If sel == initialize, initializeNonMetaClass will send +initialize
// and then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
methodListLock.lock();
// Try this class's cache.
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;
// Try this class's method lists.
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
// Try superclass caches and method lists.
curClass = cls;
// 遍历查找.
while ((curClass = curClass->superclass)) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}
// No implementation found. Try method resolver once.
if ((behavior & LOOKUP_RESOLVER) && !triedResolver) {
methodListLock.unlock();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;
done:
methodListLock.unlock();
out_nolock:
if ((behavior & LOOKUP_NIL) && methodPC == (IMP)_objc_msgForward_impcache) {
return nil;
}
return methodPC;
}
复制代码
objc_msgSend消息发送阶段总结:
如果是从class_rw_t中查找方法
- 已经排序的,二分查找.
- 没有排序的,遍历查找.
receiver通过isa指针找到receiverClass
receiverClass通过superClass指针找到superClass
2.动态解析
从上面消息发送阶段可以知道,一旦消息发送阶段找不到方法,将会进入动态方法解析.
接下来我们来验证这个过程:
首先我们注释掉示例方法实现
然后运行,提示经典报错:
-[MessageClass messageTest]: unrecognized selector sent to instance 0x100530150
复制代码
我们可以利用+resolveInstanceMethod:
和+resolveClassMethod:
动态添加方法.下面是官方解释:
按照示例,我们动态添加方法:
编译运行,可以发现正常运行,打印 customMethod
.
3.消息转发
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END