一、消息发送
-
- objc_msgSend()分析
通过之前的源码分析, 我们知道IOS的消息发送是通过 objc_msgSend(id, sel) 来进行的. 今天来具体分析一下objc_msgSend() 函数.
在源码中, 我们可以看到objc_msgSend()是定义在汇编代码中, 由于实力不够, 还看不是很懂......通过大致跟踪, 可以发现最后是调用了lookUpImpOrForward()函数. 我们可以在源码中找到这个函数的核心代码如下:
- 进入for循环后, 先进行cache判断, 如果cache_getImp()可以找到缓存的方法, 那么就直接到done_unlock返回imp
- 如果找不到缓存, 就在当前class中找meth, 如果找到的话跳到done, 进行方法缓存, 然后返回imp
- 当前类找不到的话, 将curClass赋值为superClass
- 然后在superClass中继续查找cache_getImp(), 如果找到的话跳转到done. 找不到就进入下一次递归循环
- 在for循环递归完成后, 还没有找到imp的话就说明没有实现这个方法, 就会进入到方法动态解析
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
IMP imp = nil;
Class curClass;
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 步骤1, cache 查找
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 步骤2, 当前类 查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 步骤3, curClass赋值为superClass
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// 步骤4: 查找superClass的缓存
imp = cache_getImp(curClass, sel);
if (fastpath(imp)) {
goto done;
}
}
// 步骤5: 所有的superClass都递归以后, 如果还是没有找到, 就进入消息转发流程
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
-
- getMethodNoSuper_nolock()分析
继续看下查找本类方法时的源码, 可以看到, 是通过类的methods进行for循环遍历
static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) {
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
-
- log_and_fill_cache()函数 当在类中找到对应的imp时, 会将找到的方法添加到缓存中, 代码如下: 直接将sel, imp插入到类的cache中
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
cls->cache.insert(sel, imp, receiver);
}
总结:
- iOS消息发送时会先查找当前类的缓存, 如果命中缓存, 则直接返回;
- 如果没有命中缓存, 则在本来的方法列表中查找, 如果命中, 则将方法加入缓存并返回;
- 如果没找到, 则递归到自己的父类, 同样先查找父类的缓存, 缓存找不到查找方法列表......
- 递归结束都没有找到就会进入方法动态解析
二、类中cache的设计
在上一节我们看到, 在消息发送是, 会先查找缓存, 然后在查找本类方法列表, 找到后还会将imp存到缓存中, 我们来研究下类的cache是如何设计的
-
- 类中的cache: 通过源码我们可以看到, cache 在类中声明, 是一个cache_t类型
struct objc_class : objc_object {
// ...
cache_t cache; // formerly cache pointer and vtable
// ...
}
-
- cache_t主要源码如下
struct cache_t {
public:
unsigned capacity() const; /// 散列表的容量
struct bucket_t *buckets() const; /// 真是记录缓存的散列表
Class cls() const; /// 对应的类
mask_t occupied() const; /// 散列表已占用的数量
void initializeToEmpty(); /// 初始化方法
void insert(SEL sel, IMP imp, id receiver); /// 插入方法
void copyCacheNolock(objc_imp_cache_entry *buffer, int len); /// 复制缓存
void destroy(); /// 销毁
void eraseNolock(const char *func); /// 全部擦除
};
-
- insert() 函数处理添加缓存逻辑, 其中也包含散列表扩容等操作
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// 如果散列表是空的, 就进行空间申请, 默认大小为4
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// 如果占用空间小于 3/4 or 7/8 则不做处理 继续使用
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
}
#endif
else {
// 如果超过的话, 会进行 *2 倍的大小扩容, 但是不能超过MAX_CACHE_SIZE(1<<16)
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets(); // 拿到散列表
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m); // 根据sel 和 当前容量计算哈希
mask_t i = begin;
do { // 根据哈希将sel放进散列表, 如果当前idx已存放了imp, 则进行cache_next()计算新的idx
if (fastpath(b[i].sel() == 0)) {
incrementOccupied(); // 将当前占用空间+1 然后更新成员属性
// 加入散列表
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
/** cache_next其实就是将 上次计算的结果i - 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
*/
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
三、找不到方法实现后的动态方法解析和消息转发
动态方法解析
-
- 开发者可以手动实现以下方法, 来通过runtime动态添加方法实现
// 类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
// 使用runtime动态添加方法
}
// 实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 使用runtime动态添加方法
}
-
- 在消息发送的最后, 如果找不到方法实现, 会调用resolveMethod_locked()函数进入动态方法解析
static IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
if (! cls->isMetaClass()) {
// 如果是对象, 则尝试调用类对象的方法 [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// 如果是类对象, 会先尝试调用元类对象的方法 [nonMetaClass resolveClassMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
// 然后在尝试调用 [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
}
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
-
- resolveInstanceMethod()会找到开发者实现的sel
static void resolveInstanceMethod(id inst, SEL sel, Class cls) {
// 定义 resolveInstanceMethod: 方法
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 然后去cache和类方法列表中查找, 看开发者是否实现, 如果没实现则直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// 将查找结果缓存起来, 防止以后多次进入动态方法解析
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
// ...
}
如果resolveInstanceMethod: 无法找到对应实现, 那么lookUpImpOrForward() 函数就会返回nil, 这是就会进入消息转发的流程
消息转发
-
- 首先会调用- (id)forwardingTargetForSelector:(SEL)aSelector{} 方法(以对象方法为例, 类方法相同)
在forwardingTargetForSelector: 中返回一个实现了对应方法的对象, 然后会调用 objc_msgSend(返回的对象, imp)进行消息发送
- (id)forwardingTargetForSelector:(SEL)aSelector {
}
-
- 如果在forwardingTargetForSelector返回nil, 则会调用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{} 方法
在methodSignatureForSelector: 方法中返回一个OC方法定义(如 "v@0:8"), 然后就会执行 - (void)forwardInvocation:(NSInvocation *)anInvocation{} 方法.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
-
- 如果所有methodSignatureForSelector:返回的是nil, 那么就会报错 doesNotRecognizeSelector