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