美文网首页
objc_megSend 浅析学习

objc_megSend 浅析学习

作者: 竹屋听雨 | 来源:发表于2019-12-27 18:39 被阅读0次
objc_msgSend-2.png

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*/);
}
下节继续

相关文章

网友评论

      本文标题:objc_megSend 浅析学习

      本文链接:https://www.haomeiwen.com/subject/qyldoctx.html