OC-从方法的汇编层看消息转发流程

152 阅读4分钟

一·汇编层sel & imp

ENTRY _objc_msgSend

cmp p0,#0 //对象传入#0 与p0比较 这一步是nil check

``````

ldr p13,[x0] //p13=isaGetClassFromIsa_p16 p13 //p16=classCacheLookup  Normal,objc_msgSend(sel,imp).macro GetClassFromIsa_p16mov p16, $0tbz p16adrp x10,_objc_indexed_classesadd x10,x10,_objc_indexed_classes //MASK掩码ubfx p16,p16 #ISA_INDEX_SHIFT,#ISA_INDEX_BITS //extra 属性->引用计数ldr p16,[x10,p16,UXTP #PTRSHIFT]and p16 , $0,#ISA_MASK#CACHE define 2*sizeof_ponter =16.macro CacheLookupldr p11 [x16 , #CACHE]

//p11 = mask | buckets 将x16与CACHE相加存储到寄存器p11中 这一步的操作需要配合objc_class类结构当中,通过逻辑位移ISA 8byte + superclass 8byte cache=16bytes CACHE定义在上面的macho语句中

and p10,p11 #0x000fff```f

//p10=buckets p11 和0x00ffff```f进行“与&”操作 结果存到p10中 抹掉mask

and p12 , p1 ,p11 LSR #48

//p11右移48位LSR后和p1对象 cmd & sel 赋值给p12 得到x12 这一步得到哈希下标 既cache_hash x12=_cmd&mask 这一步在cache结构中有。

add p12,p10,p12,LSL #1+SHIFT

//p12=buckets+((cmd&mask)<<(1+preshift) 将索引index左移1+3位 赋值给p12 在和 p10相加赋值给p12 。地址的平移单位是一个bucket的大小sel 8bytes imp 8bytes

ldp p17,p9,[x12]

//{imp,sel}=*bucket 将bucket里的imp赋值给p17 sel赋值给p9

**cmp p9,p1 //**如果p9=p1

b.ne 2f //如果不是 继续循环查找loop

CacheHit $0 //调用或者返回imp

//如果没有命中缓存

CacheMiss $0 //bucket->sel==0 nil

cmp p12,p10 // 如果当前的bucket是头buckets[0]

b.eq 3f //loop

ldp p17, p9,[x12 , #-BUCKET_SIZE]! //{imp,sel}= *--bucket 减减操作

b lb//循环操作

//比较对象p1 p9是否一致 最后一步说明了sel和imp是如何通过汇编层面进行绑定的

JumpMiss $0

.macro JumpMiss //jumpmiss 内存没有命中会有三种不同的处理方式 走三种不同的函数

.if $0 ==GETIMP

b GetImpMiss

.elseif $0 ==NORMAL

b __objc_msgSend_uncached

.elseif $0 == LOOKUP

b __objc_msgLookup_uncached

GetImpMiss:

mov p0,#0

ret //返回空值

END ENTRY _cache_getImp

注释:mask | bucket 中 mask处于高16位,bucket处于低48位

如果缓存找不到该方法调用

LookupImpOrForward 二分查找从isa-superclass 从类-元类-跟元类逐步查找

如果还是无法找到报出一个经典的错误

unregized selector sent to instance

objc_msgSend_uncatched

二·容错

LookupImpOrForward

{

1.继续从缓存里查看该imp是否存在在缓存里,有可能是因为多线程调用导致找不到该sel

imp = cache_getImp(cls,sel);      forward_imp = objc_msgForward_impcache 这个方法在runtime.mm (void *)objc_defaultForwardHandler

2.runtime.lock

3.checkIsKnownClass{set = objc::allocatedClass.get() } //判断是否是已知的类

4.realizeClassWithoutSwift(cls,nil){

识别类方法从cleanMemory 拿到cls的data

4.1.ro = cls->data() 这是之前类结构开的上帝视角知道的

4.2.isMeta = ro->flags & RO_META

4.3.给脏内存rw=cls->data();赋值

4.4.ro = cls->data()->ro();

4.5.从类得到数据data后执行递归操作

4.5.1 superclass = realizeClassWithoutSwift(cls->superclass,nil)

4.5.2 metaclass = realizeClassWithoutSwift (cls->ISA() , nil) 这里就是著名的isa走位图 和 继承类的走位图实

4.6 更新类的父类和元类

cls->superclass = superclass

cls->initClassIsa(metaclass)

}

5.如果superclass存在

addSubclass(superclass,cls) 这里运用了基础的数据结构:双向链表

6.实例化类methodlizedClass{

有了父类 元类

6.1给rw ro rwe ismeta赋值

isMeta=cls->isMetaClass()

rw = cls->data()

ro = rw->ro();

rwe = rw->ext();

6.2设置类方法列表,属性列表,协议列表

6.2.1 prepareMethodList

6.2.2 ro->baseProperties/rwe->properties.attachLists

6.2.3 ro->baseProtocols/rwe->protocols.attachLists

}

7.查找当前类是否有这个方法getMethodNoSuper_nolock(curClass,sel){

findMethodInSortedMethodList(key,*list) 排序 二分查找{                                     probe= base + (count >> 1);                                     probe-- (--尾序查找从分类里的方法)                                     probe++ (++头序查找从根类里的方法)                             }

如果找到了执行

log and cache_fill

内存填充函数在前篇类的内存结构有介绍

}

8.如果nolock找自己没有找到,找父类curClass=curClass->superclass == nil的缓存这里循环

循环第二次 curClass就是父类,父类的上一级是根类NSObjcet,如果还是没有找到返回nil

根元类的上级是nil 那么判断通过

9.没有发现imp,尝试方法resolver 一次最终手段,在报错前的挣扎 重新查询。著名的“动态方法决议来了”

ps;iOS开发交流技术群:欢迎你的加入,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长

if behavior & LOOKUP_RESOLVER 动态方法决议进入条件 与操作一次

behavior ^= LOOKUP_RESOLVER; 取反一次

resolveMethod_locked{

9.1.判断当前接收者sel是不是类,如果是进入类的动态方法决议/不是进入对象动态方法决议

9.1.1 resolveClassMethod

9.1.2 resolveInstanceMethod //chances are that calling the resolver have populated the cache so attempt using it

9.2 forwardingTargetForselector 快速转发

9.2.1 return sel ->messageHandle

9.2.2 return nil 又会进入一层判断

9.2.2.1 methodSignatureForSelector

return nil 报错

9.2.2.2 return signature 返回信号

调用forwardingInvocation return messagehandle

}

}

注释:动态方法决议会走两次 第一次:**resolveInstanceMethod.resolved msg(cls,resolve_sel,sel)**调用一次

第二次:resolveMethod_locked再调用一次

经典报错

来了,万恶之源来了。

#objc_defaultForwardHandler{       _objc_fatal("%c[%s %s]unrecognized selector sent to instance %p"  "no message forward handler is installed",  class_isMetaClass(object_getClass(self))?'+':'-', object_getClassName(self),sel_getName(sel),self);      )}

三·动态方法决议

那么如何调用这个动态方法决议呢?

+(BOOL)resolveInstanceMethod:(SEL)sel{      if (sel == @selector(你缺失的sel方法名)){           IMP imp = class_getMethodImplementation(self,@selector());          Method  方法=class_getInstanceMethod(self,@selector());          const char *type =method_getTypeEncoding()          return class_addMethod(self,sel,imp,type);     }     return [super resolvenInstanceMethod:sel];}

为什么类动态方法决议经过后会再经过一次对象动态方法决议?

+(BOOL)resolveClassMethod:(SEL)sel{      if (sel == @selector(你缺失的sel方法名)){           IMP imp = class_getMethodImplementation(objc_getMetaClass(""),@selector());          Method  方法=class_getInstanceMethod(objc_getMetaClass(""),@selector());          const char *type =method_getTypeEncoding()          return class_addMethod(objc_getMetaClass(""),sel,imp,type);     }     return [super resolvenInstanceMethod:sel];}

如果写在NSObjcet里,需要return NO

动态方法决议可以用来做SDK切面

四·消息转发

objcMsgLogEnabled 这个开关控制着触发消息转发的flag

需要添加extend void instru```来调用

instrumentObjcMessageSend(YES)

classmethod

instrumentObjcMessageSend(YES)

第一次调用时会把classmethod的消息转发信息保存在/tmp/msgSend-%d中

快速转发

-(id)forwardingTargetForselector:(SEL)aSelector{     NSLog(@"%@",NSStringFromSelector(aSelector))     return [object alloc]}

慢速转发

信号转发

需要信号+invocation

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{      NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));     return [NSMethodSignature signatureWithObjCTypes:"v@:"];}-(void)forwardInvocation:(NSInvocation *)anInvocation{     NSLog(@"%s-%@",__func__,anInvocation);    anInvocation.target=[Object alloc];    [anInvocation invoke];}