iOS 消息发送查找&转发流程
objc源码下载地址
通过断点结合C++源码调试流程
汇编部分
OC中调用方法其实就是给类发送消息objc_msgSend -> _objc_msgSend_uncached ->MethodTableLookup->_class_lookupMethodAndLoadCache3(id, SEL, Class)
objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend.png
_objc_msgSend_uncached.png
lookUpImpOrForward.png
消息发送流程
- 首先检查这个sel是否需要忽略,如有垃圾回收装置就不会理会retain,release等等
typedef struct {
SEL name; // same layout as struct old_method
void *unused;
IMP imp; // same layout as struct old_method
} cache_entry;
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
• 检查selector是否是垃圾回收方法,则将 IMP 结果设为 _objc_ignored_method。如果是则填充缓存_cache_fill(cls, (Method)entryp, sel);(这里entryp的类型是结构体cache_entry,将其强转为Method,并让methodPC指向该方法的实现即entryp->imp(_objc_ignored_method),然后跳转到done语句标号。否则进行下一步
- 检查这个selector是否为nil,OC允许对一个nil对象执行任何方法不会crash,因为运行时会忽略掉
伪代码
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
• lookUpImpOrNil方法中会调用lookUpImpOrForward函数查找一边缓存 此时传入的cache默认是YES.所以在后面_class_lookupMethodAndLoadCache3中默认的cache时NO,避免两次搜索缓存。
• _objc_msgForward_impcache 汇编程序入口作为缓存中消息转发的标记
• 如果imp==_objc_msgForward_impcache 直接return nil.
• 负责直接返回
• 这个方法不会进行消息的转发,而直接返回nil,这个倒是比较有趣,明明调用lookUpImpOrForward可以直接进行消息转发,可是这里偏不这样做,调用消息转发返回nil的函数,然后判断imp为nil时,自己手动返回_objc_msgForward,进行消息转发。
⚠️ class_getMethodImplementation(),method_getImplementation()返回值会不一样?
IMP method_getImplementation(Method m)
{
return m ? m->imp : nil;
}
如果这个method不存在,直接返回nil,而
class_getMethodImplementation()会经历消息转发机制,最后返回的是forwardInvocation的结果,而这部分是不开源的,也不知道具体是怎么返回的,但每次运行确实是会返回的一个固定的地址,这个地址可能和NSInvocation这个对象的内存地址有关.
- 查找这个类的实现的IMP,先存缓存的方法列表cache中查找,执行过的方法会缓存在该列表中,如果找到了就会运行对性的函数执行相应的代码
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)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;
methodListLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
• initialize默认是no,关联一下initialize的调用,其实是走的消息转发流程
//类对象都还没有初始化 ,并不影响initialize 。实力对象是不依赖 initialize
}
//
retry:
methodListLock.lock();
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
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 {
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 (resolver && !triedResolver) {
methodListLock.unlock();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}
_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;
done:
methodListLock.unlock();
return methodPC;
• objc_msgSend最开始就在缓存中进行了搜索,所以有了一个很有趣的方法_class_lookupMethodAndLoadCache3,这个方法在调用lookUpImpOrForward时传入cache是NO,避免两次搜索缓存),因为在上面lookUpImpOrNil方法中已经查找过一遍缓存。
• methodPC = _cache_getImp(cls, sel);根据cls和sel在本类缓存中查找有没有这个方法,如果有直接返回,
• 释放检测: _class_getFreedObjectClass:检测发送消息的对象是否已经被释放,如果已经释放,则返回_freedHandler 的IMP
// Check for freed class
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
- 如果没有找到,就在该类的缓存中和方法列表methodlist中查找是否有相应的方法,找到则执行
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;
}
• methodListLock.lock():考虑运行时方法的动态添加,加锁是为了使方法搜索和缓存填充成为原子操作。否则category添加时刷新的缓存可能会因为旧数据的重新填充而被完全忽略掉。
• 上一步缓存中没有发现,进入类的class's cache缓存中查找,找到了就执行goto done,否则下一步
• 类的缓存中没有找到,然后进入本类的方法列表中查找,如果找到了就进行 goto done,否则下一步
• 如果以上都没找到的话 ,下一步
- 如果没有找到,则沿着继承树在父类中查找,一直找到NSObject为止
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;
}
}
• while循环在父类中寻找 父类中也是同样的,先在父类的缓存Superclass cache,没有再找父类的方法列表Superclass method list.
• 如果找到了就执行 log_and_fill_cache加入缓存
父类查找流程图:
supperClass.png
- 如果还是没有找到,则执行消息转发流程
消息转发流程:
No implementation found. Try method resolver once.只会执行一次
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {//不是元类
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {//是元类
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
// 第一阶段 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)selector//对象方法
+ (BOOL)resolveClassMethod:(SEL)selector //处理的是类方法
{
if (sel == @selector(run:)) {
SEL readSEL = @selector(readBook);
Method readM= class_getInstanceMethod(self, readSEL);
IMP readImp = method_getImplementation(readM);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
// class_addMethod([self class], sel, (IMP)testRun, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 第二阶段:第三者的处理
- (id)forwardingTargetForSelector:(SEL)selector{
if (aSelector == @selector(run:)) {
thridTest * p = [thridTest new];
return p;
}
return [super forwardingTargetForSelector:aSelector];
}
// 第三阶段: 标准消息转发流程
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if([NSStringFromSelector(aSelector) isEqualToString:@"run:"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = [anInvocation selector];
thridTest *p = [[thridTest alloc] init];
if ([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[[thridTest alloc] init]];
return ;
}
return [super forwardInvocation:anInvocation];
}
// 第四阶段:报错
- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"不能识别的方法: %@",NSStringFromSelector(aSelector));
}
void testRun(id self ,SEL _cmd ,NSString* str){
NSLog(@"1234567890");
}
消息查找流程图:
消息查找流程.png
最后补充一下cache_t的知识
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
//删除了一些内容
}
• 首先是一个结构体
• _buckets:数组,是bucket_t结构体的数组,bucket_t是用来存放方法的SEL内存地址和IMP的。
• _mask的大小是数组大小 - 1,用作掩码。(因为这里维护的数组大小都是2的整数次幂,所以_mask的二进制位000011, 000111, 001111)刚好可以用作hash取余数的掩码。刚好保证相与后不超过缓存大小。
• _occupied是当前已缓存的方法数。即数组中已使用了多少位置。
缓存策略:
_mask->capacity()->expand()
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);//这里将sel转化为cache_key_t,也就是数字,主要是为了方便查找、。
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {//如果缓存为空
// Cache is read-only. Replace it.
//执行清理操作
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
//当容量超过capacity的四分之三时,执行扩容的逻辑。
}
else {
// Cache is too full. Expand it.
//扩容为原来的两倍
cache->expand();
}
// 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(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();//如果没有找到缓存,那么就将这个方法缓存起来。
bucket->set(key, imp);
}
扩容清理函数
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed();
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
assert(newCapacity > 0);
assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
• 首先获取旧的buckets。
• 再根据容量初始化一个新的buckets。
• 再讲新的buckets和mask设置上去,并将_occupied清0。
• 最后将旧的buckets释放掉。
cache在执行扩容的同时会清理掉旧的buckets,也就是说,之前缓存的方法会被清空,这是LRU淘汰算法的一个应用。
缓存流程
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
* cache_fill (acquires lock)
* cache_expand (only called from cache_fill)
* cache_create (only called from cache_expand)
* bcopy (only called from instrumented cache_expand)
* flush_caches (acquires lock)
* cache_flush (only called from cache_fill and flush_caches)
* cache_collect_free (only called from cache_expand and cache_flush)