Runtime

作者: 微笑_d797 | 来源:发表于2019-02-13 16:41 被阅读0次

    简介

    OC是一门动态语言所有的方法都是由运行时进行。
    Objective-C 语言将决定尽可能的从编译和链接时推迟到运行时。只要有可能,Objective-C 总是使用动态 的方式来解决问题。这意味着 Objective-C 语言不仅需要一个编译器,同时也需要一个运行时系统来执行 编译好的代码。这儿的运行时系统扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。
    Runtime的作用是
    能动态产生/修改一个类,一个成员变量,一个方法

    Runtime调用有三种方式

    1. NSObject的调用
    2. Runtime的Api
    3. selctor
    image.png

    SEL、IMP、METHOD

    selector顾名思义就是选择器,在ios开发中SEL就是可以根据一个SEL选择对应的方法IMP。SEL只是描述了一个方法的格式,如果把方法名理解成第一个标签,SEL就是描述一种由几个标签构成的方法,更偏向于c里的函数声明,SEL并不会指向方法。SEL只和方法标签格式有关,并不绑定类,对于一个多态的方法,可以用同一个SEL去调用。

    IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。

    METHOD Method 对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个 objc_method 结构体指针

    /// Method
    struct objc_method {
        SEL method_name; 
        char *method_types;
        IMP method_imp;
    };
    

    Selector,Method,IMP 它们之间的关系可以这么解释:
    一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。

    class_addIvar、class_addPropetry

    转自这里

    对于已经存在的类我们用class_addProperty方法来添加属性,而对于动态创建的类我们通过class_addIvar添加属性 , 它会改变一个已有类的内存布局,一般是通过objc_allocateClassPair动态创建一个class,才能调用class_addIvar创建Ivar,最后通过objc_registerClassPair注册class。

      struct objc_ivar {
         char *ivar_name;
         char *ivar_type;
         int ivar_offset;
      #ifdef __LP64__
         int space;
      #endif
      } 
    

    Ivar是objc_ivar的指针,包含变量名,变量类型等成员。

    ivar的相关操作

      //获取Ivar的名称
      const char *ivar_getName(Ivar v);
      //获取Ivar的类型编码,
      const char *ivar_getTypeEncoding(Ivar v)
      //通过变量名称获取类中的实例成员变量
      Ivar class_getInstanceVariable(Class cls, const char *name)
      //通过变量名称获取类中的类成员变量
      Ivar class_getClassVariable(Class cls, const char *name)
      //获取指定类的Ivar列表及Ivar个数
      Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
      //获取实例对象中Ivar的值
      id object_getIvar(id obj, Ivar ivar) 
      //设置实例对象中Ivar的值
      void object_setIvar(id obj, Ivar ivar, id value)
    

    ivar的使用

     //在运行时创建继承自NSObject的People类
      Class People = objc_allocateClassPair([NSObject class], "People", 0);
      //添加_name成员变量
      BOOL flag1 = class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
      if (flag1) {
          NSLog(@"NSString*类型  _name变量添加成功");
      }
      //添加_age成员变量
      BOOL flag2 = class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
      if (flag2) {
          NSLog(@"int类型 _age变量添加成功");
      }
      //完成People类的创建
      objc_registerClassPair(People);
      unsigned int varCount;
      //拷贝People类中的成员变量列表
      Ivar * varList = class_copyIvarList(People, &varCount);
      for (int i = 0; i<varCount; i++) {
          NSLog(@"%s",ivar_getName(varList[i]));
      }
      ///释放varList
      free(varList);
      //创建People对象p1
      id p1 = [[People alloc]init];
      //从类中获取成员变量Ivar
      Ivar nameIvar = class_getInstanceVariable(People, "_name");
      Ivar ageIvar = class_getInstanceVariable(People, "_age");
      //为p1的成员变量赋值
      object_setIvar(p1, nameIvar, @"张三");
      object_setIvar(p1, ageIvar, @33);
      //获取p1成员变量的值
      NSLog(@"%@",object_getIvar(p1, nameIvar));
      NSLog(@"%@",object_getIvar(p1, ageIvar));
    

    propretry

      ///特性
      typedef struct {
          const char *name;           ///特性名称
          const char *value;          ///特性的
      } objc_property_attribute_t;
    

    propretry 的相关操作

      //替换类中的属性
      void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
      //获取类中的属性
      objc_property_t class_getProperty(Class cls, const char *name)
      //拷贝类中的属性列表
      objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
      //获取属性名称
      const char *property_getName(objc_property_t property)
      //获取属性的特性
      const char *property_getAttributes(objc_property_t property) 
      //拷贝属性的特性列表
      objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
      //拷贝属性的特性的值
      char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
    

    propretry 的使用

      Class People = objc_allocateClassPair([NSObject class], "People", 0);
      objc_registerClassPair(People);
      ///T
      objc_property_attribute_t attribute1;
      attribute1.name = "T";
      attribute1.value=@encode(NSString*);
      ///Noatomic
      objc_property_attribute_t attribute2 = {"N",""};//value无意义时通常设置为空
      ///Copy
      objc_property_attribute_t attribute3 = {"C",""};
      ///V_属性
      objc_property_attribute_t attribute4 = {"V","_name"};
      ///特性数
      objc_property_attribute_t attributes[] ={attribute1,attribute2,attribute3,attribute4};
      //向People类中添加名为name的属性,属性的4个特性包含在attributes中
      class_addProperty(People, "name", attributes, 4);
      //获取类中的属性列表
      unsigned int propertyCount;
      objc_property_t * properties = class_copyPropertyList(People, &propertyCount);
      for (int i = 0; i<propertyCount; i++) {
          NSLog(@"属性的名称为 : %s",property_getName(properties[i]));
          NSLog(@"属性的特性字符串为: %s",property_getAttributes(properties[i]));
      }
      //释放属性列表数组
      free(properties);
    

    objc_msg_send()

    简介

    我们知道OC的函数调用是消息发送机制,那么消息发送机制是如何实现的呢。

    我们将main.m文件编译成c++文件通过 image.png
    一共9w多行代码只需要取最后
    // -(void) eat;
    /* @end */
    
    #pragma clang assume_nonnull end
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_b2_hs7ds2bd5zz7d752kk495bhw0000gn_T_main_f668c6_mi_0);
    
            Animals * animal = ((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animals"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)animal, sel_registerName("eat"));
    
        }
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
    

    objc_msgSend(void /* id self, SEL op, ... */ )
    当类初始化时候 显示获取 id self,即 (id)objc_getClass("Animals"),就是根据类名取获取这个类,然后alloc,init就是 #selector(alloc) 其底层实现是 sel——registerName("alloc/init"),其目的就是为了查找该类里面有没该方法
    第二句同理target是已经生产的animal selector是 eat方法 sel_registerName("eat")去类的内存布局中查找eat方法

    image.png
    objc_msgsend 底层实现有两种方法一中是快速的一种是慢速的
    快速是通过汇编从响应的缓存里面找到,慢速是通过c,c++以及汇编一起完成的。
    之所以使用汇编的原因是 :
    1. c里面不会写一个函数保留未知的参数跳转到任意的指针,c无法实现,汇编可以通过寄存器直接保留

    快速查找

    image.png

    缓存 每个类的构成有一个cache,存储类的selector和IMP,IMP和selector会组成一个哈希表,然后通过hash直接查找会非常快,当查找一个方法时,首先找cache,如果有会直接返回,如果没有则会经历一个复杂而又缓慢(慢速)的过程,找到了会继续往cache里面存

    快速查找源码分析流程(汇编)

    image.png
    image.png

    objc_msg_msgSend

    汇编源码

    往下走
    LLookup_NilOrTagged /// 针对内存里寄存器进行赋值处理isa优化
    然后往下走走到
    LGetIsaDone isa /// isa处理完毕

    ///.  code...
        cmp x0, #0          // nil check and tagged pointer check
    
        b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
        ldr x13, [x0]       // x13 = isa
        and x16, x13, #ISA_MASK // x16 = class  
    LLookup_GetIsaDone:
    //  code...
    
    LLookup_NilOrTagged: /// isa优化
        b.eq    LLookup_Nil // nil check /// 为nil返回return
    
        /// tagged
        mov x10, #0xf000000000000000
        cmp x0, x10
        b.hs    LLookup_ExtTag
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        b   LLookup_GetIsaDone /// isa处理完毕
    

    处理完毕会走

    CacheLookup Normal 流程

    从缓存里取IMP: 如果缓存里有则取出来没有的话

    CahceLoopUp Normal返回(call or return imp 或者 objc_msgSend_uncached)

    1. CacheHit call imp /// 查找缓存
    2. CheckMiss - _objc_msgSend_uncached /// 找不到
    3. add /// 如果没有找到但是其他地方找到了这时候就可以add进去
    
    /********************************************************************
     *
     * 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
    
    .macro CacheHit /// cachehit
    .if $0 == NORMAL /// normal ///call imp
        MESSENGER_END_FAST
        br  x17         // call imp
    .elseif $0 == GETIMP
        mov x0, x17         // return imp
        ret
    .elseif $0 == LOOKUP
        ret             // return imp via x17
    .else
    .abort oops
    .endif
    .endmacro
    
    .macro CheckMiss
        // miss if bucket->sel == 0
    .if $0 == GETIMP
        cbz x9, LGetImpMiss
    .elseif $0 == NORMAL
        cbz x9, __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        cbz x9, __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
        // x1 = SEL, x16 = isa
        ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
        and w12, w1, w11        // x12 = _cmd & mask
        add x12, x10, x12, LSL #4   // x12 = buckets + ((_cmd & mask)<<4)
    
        ldp x9, x17, [x12]      // {x9, x17} = *bucket
    1:  cmp x9, x1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: x12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp x12, x10        // wrap if bucket == buckets
        b.eq    3f
        ldp x9, x17, [x12, #-16]!   // {x9, x17} = *--bucket
        b   1b          /// loop
    
    3:  // wrap: x12 = first bucket, w11 = mask 
        add x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)
    
        // Clone scanning loop to miss instead of hang when cache is corrupt.
        // The slow path may detect any corruption and halt later.
    
        ldp x9, x17, [x12]      // {x9, x17} = *bucket
    1:  cmp x9, x1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: x12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp x12, x10        // wrap if bucket == buckets
        b.eq    3f
        ldp x9, x17, [x12, #-16]!   // {x9, x17} = *--bucket
        b   1b          /// loop
    
    3:  // double wrap
        JumpMiss $0
        
    .endmacro
    

    然后消息走到了 __objc_msgSend_uncached,__objc_msgSend_uncached的构成是

    STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup /// 重点
        br  x17
    
        END_ENTRY __objc_msgSend_uncached
    
    
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    
    

    MethodTableLookUp --> __class_lookupMethodAndLoadCache3

    .macro MethodTableLookup
        
        // push frame
        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
        bl  __class_lookupMethodAndLoadCache3 /// 重点查找IMP
    
        // 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
    
    .endmacro
    

    __class_lookupMethodAndLoadCache3 方法为_class_lookupMethodAndLoadCache3调用的汇编语言

    完整代码流程写在注释里

    ********************************************************************
     *
     * 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
     *
     ********************************************************************
    
        .data
        .align 3
        .globl _objc_debug_taggedpointer_classes
    _objc_debug_taggedpointer_classes:
        .fill 16, 8, 0
        .globl _objc_debug_taggedpointer_ext_classes
    _objc_debug_taggedpointer_ext_classes:
        .fill 256, 8, 0
    
        ENTRY _objc_msgSend ///************************************** 1.进入objcmsgSend
        UNWIND _objc_msgSend, NoFrame
        MESSENGER_START
        /// x0 recevier
        // 消息接收者  消息名称
        cmp x0, #0          // nil check and tagged pointer check
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) //// ****************************************************2.isa 优化
        ldr x13, [x0]       // x13 = isa
        and x16, x13, #ISA_MASK // x16 = class  
    LGetIsaDone: ///**************************************************** 3.isa优化完成
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached ///*******************************************4.执行 CacheLookup NORMAL
    
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
    
        /// tagged
        mov x10, #0xf000000000000000
        cmp x0, x10
        b.hs    LExtTag
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
    
    LExtTag:
        // ext tagged
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
        
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        MESSENGER_END_NIL
        ret
    
        END_ENTRY _objc_msgSend
    
    
        ENTRY _objc_msgLookup
        UNWIND _objc_msgLookup, NoFrame
    
        cmp x0, #0          // nil check and tagged pointer check
        b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
        ldr x13, [x0]       // x13 = isa
        and x16, x13, #ISA_MASK // x16 = class  
    LLookup_GetIsaDone:
        CacheLookup LOOKUP      // returns imp
    
    LLookup_NilOrTagged:
        b.eq    LLookup_Nil // nil check
    
        /// tagged
        mov x10, #0xf000000000000000
        cmp x0, x10
        b.hs    LLookup_ExtTag
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        b   LLookup_GetIsaDone
    
    LLookup_ExtTag: 
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LLookup_GetIsaDone
    
    LLookup_Nil:
        adrp    x17, __objc_msgNil@PAGE
        add x17, x17, __objc_msgNil@PAGEOFF
        ret
        END_ENTRY _objc_msgLookup
        STATIC_ENTRY __objc_msgNil
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
        
        END_ENTRY __objc_msgNil
    
    
        ENTRY _objc_msgSendSuper
        UNWIND _objc_msgSendSuper, NoFrame
        MESSENGER_START
    
        ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached
    
        END_ENTRY _objc_msgSendSuper
    
        // no _objc_msgLookupSuper
    
        ENTRY _objc_msgSendSuper2
        UNWIND _objc_msgSendSuper2, NoFrame
        MESSENGER_START
    
        ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
        ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
        CacheLookup NORMAL
    
        END_ENTRY _objc_msgSendSuper2
    
        
        ENTRY _objc_msgLookupSuper2
        UNWIND _objc_msgLookupSuper2, NoFrame
    
        ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
        ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
        CacheLookup LOOKUP
    
        END_ENTRY _objc_msgLookupSuper2
    
    
    .macro MethodTableLookup
        
        // push frame
        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
        bl  __class_lookupMethodAndLoadCache3/// *********************************************6.方法为_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
    
    .endmacro
    
        STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup /// ********************************************** 5.查找IMP
        br  x17
    
        END_ENTRY __objc_msgSend_uncached
    
    
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    
    
        STATIC_ENTRY _cache_getImp
    
        and x16, x0, #ISA_MASK
        CacheLookup GETIMP
    
    LGetImpMiss:
        mov x0, #0
        ret
    
        END_ENTRY _cache_getImp
    

    通过_class_lookupMethodAndLoadCache3 来到c语言文件


    image.png

    下列方法实现流程是 如果缓存存在往缓存里取IMP,如果不存在往下走判断类是不是已知类然后在走相应的流程 ,判断完毕后依然需要从缓存里面取一遍,这样做事为了并发设置及的以及remap(cls)重映射,进行自己->父类->NSObject->getMethodNoSuper_nolock->log_and_fill_cache(将该IMP缓存)

    慢速查找

    /***********************************************************************
    * lookUpImpOrForward.
    * The standard IMP lookup. 
    * initialize==NO tries to avoid +initialize (but sometimes fails)
    * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
    * Most callers should use initialize==YES and cache==YES.
    * inst is an instance of cls or a subclass thereof, or nil if none is known. 
    *   If cls is an un-initialized metaclass then a non-nil inst is faster.
    * May return _objc_msgForward_impcache. IMPs destined for external use 
    *   must be converted to _objc_msgForward or _objc_msgForward_stret.
    *   If you don't want forwarding at all, use lookUpImpOrNil() instead.
    **********************************************************************/
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (cache) { /// 如果有缓存则从缓存里取
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.read();
    
        if (!cls->isRealized()) {
            // Drop the read-lock and acquire the write-lock.
            // realizeClass() checks isRealized() again to prevent
            // a race while the lock is down.
            runtimeLock.unlockRead();
            runtimeLock.write();
    
            realizeClass(cls);
    
            runtimeLock.unlockWrite();
            runtimeLock.read();
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // If sel == initialize, _class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        
     retry:    
        runtimeLock.assertReading();
    
        // Try this class's cache.
        /// 这里为啥又取了一次imp ///
       /// 1. 保证并发
      ///  2.remap(cls) -- 重映射
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlockRead();
    
        return imp;
    }
    

    消息传递转发机制

    引入头文件

    #import <objc/runtime.h>
    #import <objc/message.h>
    

    oc中的方法调用

    class Person {
      func eat() {
        code
      }
    }
    let p = Person()
    p.eat()
    

    其底层实现是

    objc_msgSend(void /* id self, SEL op, ... */ )
    objc_msgSend(p,#selector("eat"))
    

    当如果找不到eat方法会进入resolveinstanceMethod方法进行处理来进行匹配然后动态添加已有的属性列表中(添加方法重新进入慢速查找),没有则出发快速转发机制forwardTarget出发消息转发机制来实现快速转发,如果依然没有处理则进行慢速转发,方法签名+消息签名,最后如果系统给的几次机会都没有有效处理该消息则出发doesnotRecognizeSelector并抛出[unrecognized selector sent to instance 0x0000000]异常。

    相关文章

      网友评论

          本文标题:Runtime

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