objc_msgSend执行流程
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段
消息发送
动态方法解析
消息转发
相信只要做了几年开发的都非常清楚runtime的消息转发机制,今天我就带大家一起来看看objc_msgSend执行流程
objc_msgSend执行流程01-消息发送
//前面这里都是汇编,有兴趣的可以了解一下
ENTRY _objc_msgSend
b.le LNilOrTagged
b.eq LReturnZero
CacheLookup NORMAL
END_ENTRY _objc_msgSend
MethodTableLookup
bl __class_lookupMethodAndLoadCache3
//从这里开始就查找方法
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver) {
IMP imp = nil;
if (cache) {//如果缓存里面有方法 就直接返回方法地址
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
if (imp) goto done; //如果缓存里没有就去方法列表里面查找方法 class_rw_t
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {//如果找到了这个方法就填充到缓存里面去
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//去父类的缓存里面查找方法列表
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在父类中找到这个方法并且缓存到当前类中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
}
//如果经历这些还是没有找到方法就会到第二个阶段 消息解析
static void resolveMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
resolveInstanceMethod(cls, sel, inst);
}
}
}
//如果找到了就将方法填充到缓存里面去
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver) {
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
//看到这个bucket_t 是不是有点眼熟,没错这就是我们在第一节里面讲到的方法缓存散列表,最终将sel作为key,函数方法地址作为value缓存到散列表中
cache_t *cache = getCache(cls);
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set(sel, imp);
}
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel){
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
//这一步去方法列表中查找方法
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
//通过排序方法列表找到对应的方法地址
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
//二分法查找方法地址
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
//找到了对应的方法,就是我们最熟悉的method_t
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
};
//对这个大家都不陌生了吧 在runtime第一节里面我们讲到了
objc_msgSend执行流程02-动态方法解析
**resolveInstanceMethod **
**resolveClassMethod **
对于这两个方法相信大家都很熟悉 到了这里就进入我们消息转发的第二步动态方法解析
这里给大家演示一下最基本的动态添加方法
//如果没有进行动态解析才会去动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
objc_msgSend的执行流程03-消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
面试题
讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用