您的位置:首页 > 移动开发 > Objective-C

深入理解 Objective-C 的方法调用流程

2016-04-09 18:52 666 查看
转载原址:http://www.jianshu.com/p/114782a909f9

我们知道,Objective-C 的方法调用不同于其他编程语言。在 Objective-C 中,所有的
[receiver message]
都会转换为
objc_msgSend(receiver, @selector(message));


objc_msgSend
的调用又涉及到方法查找、消息动态处理等过程。下面我们结合
objc
的源码来深入了解 Objective-C 的方法调用流程。

首先了解几个概念,

SEL

打开
objc.h
,我们能看到 SEL 的定义如下:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

Objective-C 在编译时,会根据方法的名字生成一个用来区分这个方法的唯一的一个ID,本质上就是一个字符串。只要方法名称相同,那么它们的ID就是相同的。

IMP

打开
objc.h
,我们看到 IMP 的定义如下:

typedef id (*IMP)(id, SEL, ...);

实际上就是一个函数指针,指向方法实现的首地址。

通过取得 IMP,我们可以跳过 runtime 的消息传递机制,直接执行 IMP指向的函数实现,这样省去了 runtime 消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些,当然必须说明的是,这种方式只适用于极特殊的优化场景,如效率敏感的场景下大量循环的调用某方法[1]。

直接使用 IMP 执行方法调用的例子如下:

- (void)callFunctionUsingIMP
{
//Build Setting --> Enable Strict Checking of objc_msgSend Calls  改为 NO
void (*imp) (id,SEL,id) = (void (*)(id,SEL,id))[self methodForSelector:@selector(testImp:)];
imp(self,@selector(testImp:),@"hello");
}

- (void)testImp:(NSString *)string
{
NSLog(@"%@",string);
}

有两点需要注意:

需要在 Build Setting 中将 Enable Strict Checking of objc_msgSend Calls 的设置改为 NO
通过
methodForSelector
获取到 IMP 需要根据具体的参数进行类型转换,参考这个问题

Method

objc.h
中, Method 的定义如下:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
SEL method_name                                          OBJC2_UNAVAILABLE;
char *method_types                                       OBJC2_UNAVAILABLE;
IMP method_imp                                           OBJC2_UNAVAILABLE;
}

Method = SEL + IMP + method_types,相当于在SEL和IMP之间建立了一个映射。

iOS方法调用流程

objc_msgSend() Tour 系列文章通过对
objc_msgSend
的汇编源码分析,总结出以下流程:

检查 selector 是否需要忽略
检查 target 是否为 nil,如果是 nil 就直接 cleanup,然后 return
在 target 的 Class 中根据 selector 去找 IMP

寻找 IMP 的过程[2]:

在当前 class 的方法缓存里寻找(cache methodLists)
找到了跳到对应的方法实现,没找到继续往下执行
从当前 class 的 方法列表里查找(methodLists),找到了添加到缓存列表里,然后跳转到对应的方法实现;没找到继续往下执行
从 superClass 的缓存列表和方法列表里查找,直到找到基类为止
以上步骤还找不到 IMP,则进入消息动态处理和消息转发流程,详见这篇文章

我们能在
objc.h
源码中找到上述寻找 IMP 的过程,具体对应的代码如下:

/***********************************************************************
* lookUpMethod.
* The standard method lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* May return _objc_msgForward_internal. IMPs destined for external use
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
**********************************************************************/
__private_extern__ IMP lookUpMethod(Class cls, SEL sel,
BOOL initialize, BOOL cache)
{
Class curClass;
IMP methodPC = NULL;
Method meth;
BOOL triedResolver = NO;

// Optimistic cache lookup
if (cache) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}

// realize, +initialize, and any special early exit
methodPC = prepareForMethodLookup(cls, sel, initialize);
if (methodPC) return methodPC;

// 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:
lockForMethodLookup();

// 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 = _class_getSuperclass(curClass))) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, &_objc_msgForward_internal);
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 (!triedResolver) {
unlockForMethodLookup();
_class_resolveMethod(cls, sel);
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.

_cache_addForwardEntry(cls, sel);
methodPC = &_objc_msgForward_internal;

done:
unlockForMethodLookup();

// paranoia: look for ignored selectors with non-ignored implementations
assert(!(sel == (SEL)kIgnore  &&  methodPC != (IMP)&_objc_ignored_method));

return methodPC;
}

通过上述代码,我们清晰地了解到了 runtime 库寻找 IMP 的过程。

需要注意的是,在 superClass 中寻找 IMP 时,不论是在 cache methodLists 还是 methodLists 中找到 IMP,都会先存入当前 class 的 cache methodLists 再跳转到对应的方法实现。

文/hi_xgb(简书作者)

原文链接:http://www.jianshu.com/p/114782a909f9

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: