深入理解 Objective-C 的方法调用流程
来源:互联网 发布:win10电脑mac地址修改 编辑:程序博客网 时间:2024/04/30 17:53
我们知道,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 再跳转到对应的方法实现。
原文链接:http://www.jianshu.com/p/114782a909f9
- 深入理解 Objective-C 的方法调用流程
- Objective-C方法的调用流程详解
- Objective-c方法调用流程
- Objective-c方法调用流程
- 深入理解 Objective-C:方法缓存
- 深入理解Objective-C:方法缓存
- Objective-c方法调用流程 (消息)
- Objective-c 方法的调用
- Objective-c 方法的调用
- 深入理解Objective-C的Block
- 深入理解Objective-C的Block
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Runtime机制
- 深入理解Objective-C的Block
- 算法——括号匹配问题(堆栈应用)
- Java注解
- iOS性能优化—— Instruments
- java毕向东听课笔记11(异常体系)
- aaaa
- 深入理解 Objective-C 的方法调用流程
- 关于视图方面的UI控件
- JAVA Socket 底层是怎样基于TCP/IP 实现的
- 51nod1055(递推)
- cocos2dx--TextureAtlas
- class.forName()理解
- Java_SE10-多线程,TCP通信
- iOS数据库离线缓存思路和网络层封装——原理
- InnoDB存储引擎的文件简述(表空间文件和重做日志文件)