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

2020-12-11 10:33:37 浏览数 (1)

#由于贴图实在反人类,我用加粗字体来代替oc底层的源码和汇编代码

一·汇编层sel & imp

ENTRY _objc_msgSend

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

``````

ldr p13,[x0] //p13=isa

GetClassFromIsa_p16 p13 //p16=class

CacheLookup Normal,objc_msgSend(sel,imp)

.macro GetClassFromIsa_p16

mov p16, $0

tbz p16

adrp x10,_objc_indexed_classes

add 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 CacheLookup

ldr 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 一次最终手段,在报错前的挣扎 重新查询。著名的“动态方法决议来了”

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];

}

0 人点赞