objc_megSend 浅析学习
runtime:
waht's the runtime?
runtime 是一套API ,由c ,c++ 汇编写成 为oc 提供运行时功能。
legecy :objc_2.0 之前的runtime版本
Modern:objc_2.0 系统使用的runtime版本
运行时:代码加载到内存
编译时:把高级语言翻译成机器语言,oc->汇编。(llvm)command+b ->products里面编译成app ->可执行文件
runtime的使用方式:
1:Objective-C code =@selector()
我们写好oc 代码,runtime 会自动的在幕后搞定一切,调用方法,编译器把oc代码转成汇编代码,确定数据结构和调用函数。
2: NSObject的方法 =NSSelectorFromString()
我们的程序中大多数是继承自NSObject类的子类,(NSProxy 是个例外,他是抽象超类)
一般情况下,NSObject类仅仅定义了完成某些事件的模板,并没有提供所需要的代码。--例如description方法,该方法返回类内容的字符串标示。该方法主要是调试一些信息。NSOject类不知道子类的内容,所以他只是 返回类类的名字跟对象的地址,NSObject的子类可以进行重新实现。还有一些可以从runtime中获取信息。允许对对象进行自我检查。
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
//检查对象是否存在于制定的类的继承体系中(是否是其子类或者父类的和当前类的成员变量)
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
//检查对象是否实现了制定协议类的方法
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//检查对象能否相应某些制定的消息
- (BOOL)respondsToSelector:(SEL)aSelector;
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;
@end
3: sel_registerName 函数API
方法本质探索
objc_megSend 发送消息
1:(id)消息接收着
2:(sel) 方法编号 key & mask --index --->imp
对象方法:--person - sel
LGStudent *s = [LGStudent new];
[s sayCode];
// 方法调用底层编译
// 方法的本质: 消息 : 消息接受者 消息编号 ....参数 (消息体)
objc_msgSend(s, sel_registerName("sayCode"));
类方法 :--类 --sel
// 类方法编译底层
// id cls = [LGStudent class];
// void *pointA = &cls;
// [(__bridge id)pointA sayNB];
objc_msgSend(objc_getClass("LGStudent"), sel_registerName("sayNB"));
父类 :---objc_msgSendSuper
// 向父类发消息(对象方法)
struct objc_super lgSuper;
lgSuper.receiver = s;
lgSuper.super_class = [LGPerson class];
objc_msgSendSuper(&lgSuper, @selector(sayHello));
//向父类发消息(类方法)
struct objc_super myClassSuper;
myClassSuper.receiver = [s class];
myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元类
objc_msgSendSuper(&myClassSuper, sel_registerName("sayNB"));
查看汇编代码:
寄存器:
我们都知道CPU的典型构成中有寄存器、控制器和运算器等组成,部件之间通过总线连接。运算器负责信息处理,控制器负责控制其他期间进行工作,寄存器用于信息存储。对我们程序员来说寄存器是最主要部件,可以通过改变寄存器的内容来实现对CPU的控制。
ARM64 有34个寄存器,包括31个通用寄存器、SP、PC、CPSR。
ARM64拥有有31个64位的通用寄存器 x0 到 x30, ---(x0-x7 参数存储)这些寄存器通常用来存放一般性的数据,称为通用寄存器。
寄存器 | 位数 | 描述 |
---|---|---|
x0-x30 | 64bit | 通用寄存器,亦可以转为32bit使用:w0-w30 |
FP(x29) | 64bit | 保存栈帧地址(栈地指针) |
LR(X30) | 64bit | 通常称为X30为程序链接寄存器,保存跳转返回信息地址 |
SP | 64bit | 保存栈指针 |
PC | 64bit | 程序计数器,俗称PC指针,总是指向即将要执行的下一条指令。 |
- Mark:
- x0-x7:用于子程序调用时传递参数,x0还用于返回值传递;
- x8 间接寻址结果
- LR:保存子程序结束后需要执行的下一条指令
汇编种类
- 8086汇编 (8086处理器是16bit的CPU)
- win32汇编
- win64汇编
- ARM ( 嵌入式。Mac OS ,iOS )
架构 | 设备 |
---|---|
armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch |
armv7 | iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 |
armv7s | iPhone5, iPhone5C, iPad4(iPad with Retina Display) |
armv64 | iPhone6s , iphone6s plus,iPhone6, iPhone6 plus,iPhone5S ,iPad Air, iPad mini2 |
arm64 汇编常用指令:
mov :x1,x0: 将寄存器x0的值传送到寄存器x1;
add :x0, x1,x2: 寄存器x1和x2的值相加厚传送给寄存器x0;
sub :x0,x1,x2: 寄存器x1,x2的值相减后 传送到x0;
and :x0, x0 , #0xF: x0的值与0xF相位与后的值传递到x0;
orr :x0,x0, #9:x0的值与9或运算后的值传递给x0;
eor : x0,x0,#0xF; x0的值与#0xF的异或结果传递给x0;
ldr :x5,[x6, #0x08];x6寄存器加 0x08的和的地址值内的数据传递到x5;
str :x0,[sp,#0x8];x0寄存器的数据传送到sp + 0x8地址值指向的存储空间
stp :x29,x30,[sp,#0x10];入栈指令
ldp :x29,x30,[sp,#0x10];出栈指令
cbz:比较(compare),如果结果为零,就转移(只能跳转到后面的指令)
cbnz:比较,如果结果非零就转移 (只能跳转到后面的指令)
cmp :比较指令,相当于subs,影响程序状态寄存器
cpsr :b/bl 绝对跳转 #imm,返回地址保存在lr(x30)
ent:子程序返回指令,返回地址默认保存在lr(x30)
### objc_megSend汇编实现 流程查看 -- 快速流程 --
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
//进入 _objc_msgSend
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//对比
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone: 查找ISA 完毕
查看缓存里是否已经有了方法,如果存在就直接返回就可以了
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
/********************************************************************
* GetClassFromIsa_p16 src
* src is a raw isa field. Sets p16 to the corresponding class pointer.
* The raw isa might be an indexed isa to be decoded, or a
* packed isa that needs to be masked.
*
* On exit:
* $0 is unchanged
* p16 is a class pointer
* x10 is clobbered
********************************************************************/
#if SUPPORT_INDEXED_ISA
.align 3
.globl _objc_indexed_classes
_objc_indexed_classes:
.fill ISA_INDEX_COUNT, PTRSIZE, 0
#endif
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
/********************************************************************
*
* CacheLookup NORMAL|GETIMP|LOOKUP
* 缓存查找 正常 |快速|慢速查找
* Locate the implementation for a selector in a class method cache.
*
* Takes:
* x1 = selector
* x16 = class to be searched
*
* Kills:
* x9,x10,x11,x12, x17
*
* On exit: (found) calls or returns IMP
* with x16 = class, x17 = IMP
* (not found) jumps to LCacheMiss
*
********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// CacheHit: x17 = cached IMP, x12 = address of cached IMP
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro CacheLookup
// p1 = SEL, p16 = isa 地址偏移 找到cache_t -->bucket occupied|mask
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp 缓存命中 imp并返回
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
// isa - 类
// cache_t - bucket
// 方法 - bit - rw - ro - methodList
// 汇编 - 未知 参数
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
//准备条件充分之后 进入_class_lookupMethodAndLoadCache3
bl __class_lookupMethodAndLoadCache3
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
下节继续