前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC-从方法的汇编层看消息转发流程

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

原创
作者头像
Wilbur-L
修改2020-12-11 10:33:37
8350
修改2020-12-11 10:33:37
举报
文章被收录于专栏:iOS底层原理iOS底层原理

#由于贴图实在反人类,我用加粗字体来代替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];

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一·汇编层sel & imp
  • 二·容错
    • LookupImpOrForward
      • 经典报错
      • 三·动态方法决议
      • 四·消息转发
        • 快速转发
          • 慢速转发
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档