Runtime源码阅读分享之对象的本质,了解isa

image

引言

我们都知道,Runtime 是 Objective-C 这门动态语言的核心,只有理解了它,我们才能够更好的理解 Objective-C 到底是如何工作的,在编程时,也会更加得心应手。由于时间和精力有限,此次我主要想从以下几方面来进行 Runtime 源码的阅读,日后将会逐步完善。由于总体篇幅较长,所以我将会每一部分拆分成一篇文章来具体分析。

目录

一、对象的本质,了解 isa

二、对象的生命周期

三、对象的引用计数

四、对象的扩展方法

五、Runtime 的应用

对象的本质,了解 isa

首先来看一看对象和类的定义

/// Represents an instance of a class.
struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
};


// 其内部有一个指向 Class 的指针,而 Class 是什么呢

// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

// 再来看一下  objc_class 的定义,需要注意的是,objc_class 也是继承自 objc_object, 由于 objc_object 中已经定义了一个 isa 指针,由于结构体中所有的成员都是 public 的,所以 objc_class 也就拥有了 isa 并且也拥有访问 isa 的权限。
struct objc_class : objc_object {
    // Class ISA;   // 此时,类中的 isa 指针指向的是 metaClass 元类
    Class superclass;  // 父类
    cache_t cache;             // formerly cache pointer and vtable 类的方法缓存,因为 Runtime 时会把第一次遇到的方法缓存到方法缓存中,此后将直接从缓存中读取方法,极大提高了效率
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
  //  class_data_bits_t <==> class_rw_t + 一个 rr/alloc 位
};

所以到这里,我们也就理解到了,实际上,类也是一个对象

  • 我们需要知道的是,在 Objective-C 中,对象的方法并没有存储于对象的结构体中(如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响)。在调用实例方法时,而是通过 isa 指针来寻找相应的类,通过 class_data_bits_t 来寻找类中的方法。具体是如何寻找的,我们看👇

    // 首先 class_data_bits_t 中有一个 bits 位
    struct class_data_bits_t {
    
        // Values are the FAST_ flags above.
        uintptr_t bits;
     public:
    
      // 这里返回的数据是 class_rw_t* 指针类型的数据,在这个方法中我们可以看出,将 bits 与 FAST_DATA_MASK 进行位运算,只取其中的 [3, 47] 位转换成 class_rw_t * 返回。
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
    }
    
    // data() 返回的是一个 class_rw_t* 指针, class_rw_t 又是什么?
    
    // 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
        uint32_t index;
    #endif
    };
    // 由此我们可以看出, class_rw_t 中包含了一些关于类的信息,比如 flag, 版本号, 方法数组, 属性数组等。而其中又有一个指向 class_ro_t 的指针, class_ro_t 又是什么?
    // 原来,class_ro_t 中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    }; 
    
  • 所以由此,我们知道 class_ro_t 保存的是在编译期时就已经确定的方法,所以当在编译期时, class_data_bits_t 将直接指向 class_ro_t ,而后在 Runtime 时,将会调用 class_data_bits_t 的 data() 直接将结果从 class_rw_t 转化成 class_ro_t 指针, 然后再初始化一个 class_rw_t 指针,此时它中的数据都为空,然后再设置它的 ro 变量和 flag, 最后再为其设置正确的 data

    /***********************************************************************
    * realizeClass
    * Performs first-time initialization on class cls, 
    * including allocating its read-write data.
    * Returns the real class structure for the class. 
    * Locking: runtimeLock must be write-locked by the caller
    **********************************************************************/
    static Class realizeClass(Class cls)
    {
        runtimeLock.assertWriting();
    
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
    
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        assert(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
    
      // 强制转化为 ro
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
          // 这时候首先给 rw 分配内存空间并且初始化为 0
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
          // 使 rw 指向 ro
            rw->ro = ro;
          // 设置 rw 的 flag 为 正在初始化和已经初始化
    /**
          // class is realized - must never be set by compiler
              #define RO_REALIZED           (1<<31)
    
          // Values for class_rw_t->flags
          // These are not emitted by the compiler and are never used in class_ro_t. 
          // Their presence should be considered in future ABI versions.
          // class_t->data is class_rw_t, not class_ro_t
              #define RW_REALIZED           (1<<31)
    */
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
      // 判断是否为 metaClass  RO_META (1 << 0)
        isMeta = ro->flags & RO_META;
    
        rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
        // Choose an index for this class.
      // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
      cls->chooseClassArrayIndex();
    
      if (PrintConnecting) {
          _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", 
                       cls->nameForLogging(), isMeta ? " (meta)" : "", 
                       (void*)cls, ro, cls->classArrayIndex());
      }
    
      // Realize superclass and metaclass, if they aren't already.
      // This needs to be done after RW_REALIZED is set above, for root classes.
      // This needs to be done after class index is chosen, for root metaclasses.
      supercls = realizeClass(remapClass(cls->superclass));
      metacls = realizeClass(remapClass(cls->ISA()));
      #if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa for some classes and/or platforms.
      // Set instancesRequireRawIsa.
      bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
      bool rawIsaIsInherited = false;
      static bool hackedDispatch = false;
    
      if (DisableNonpointerIsa) {
          // Non-pointer isa disabled by environment or app SDK version
          instancesRequireRawIsa = true;
      }
      else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
               0 == strcmp(ro->name, "OS_object")) 
      {
          // hack for libdispatch et al - isa also acts as vtable pointer
          hackedDispatch = true;
          instancesRequireRawIsa = true;
      }
      else if (supercls  &&  supercls->superclass  &&  
               supercls->instancesRequireRawIsa()) 
      {
          // This is also propagated by addSubclass() 
          // but nonpointer isa setup needs it earlier.
          // Special case: instancesRequireRawIsa does not propagate 
          // from root class to root metaclass
          instancesRequireRawIsa = true;
          rawIsaIsInherited = true;
      }
      
      if (instancesRequireRawIsa) {
          cls->setInstancesRequireRawIsa(rawIsaIsInherited);
      }
        // SUPPORT_NONPOINTER_ISA
      #endif
        
        // Update superclass and metaclass in case of remapping
      cls->superclass = supercls;
      cls->initClassIsa(metacls);
    
      // Reconcile instance variable offsets / layout.
      // This may reallocate class_ro_t, updating our ro variable.
      if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
      // Set fastInstanceSize if it wasn't set already.
      cls->setInstanceSize(ro->instanceSize);
    
      // Copy some flags from ro to rw
      if (ro->flags & RO_HAS_CXX_STRUCTORS) {
          cls->setHasCxxDtor();
          if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
              cls->setHasCxxCtor();
          }
      }
    
      // Connect this class to its superclass's subclass lists
      if (supercls) {
          addSubclass(supercls, cls);
      } else {
          addRootClass(cls);
      }
    
      // Attach categories
    // 在这个方法中将 rw 的方法列表,属性列表,协议列表赋值
      methodizeClass(cls);
    
      return cls;
    };
    

此时,经过 Runtime 的作用之后,现在内存中的关系是,类中的 data 指针指向 class_data_bits_t, class_data_bits_t 结构体中的 data() 方法获取到的是 class_rw_t 指针, class_rw_t 结构体中的 ro 指针指向 class_ro_t。图如下:

图片来自一位大神的博客,侵权删
  • 但是问题来了,类的方法是如何被查找和调用的呢?由于我们已经知道了,在 ObjC 中,实际上类也是一个特殊的对象,查找类的方法实际上就和查找实例方法采用同样的机制,但是如何才能让他们采用同样的机制呢?这时,元类的作用就显现了出来。

    • metaClass 保证了类中也有一个指向 Class 类型的指针,保证了类和对象的一致性,保证了类查找方法的机制与对象查找方法的机制保持同步。
      • 当实例方法调用时,通过对象的 isa 在类中获取方法的实现
      • 当类方法调用时,通过类的 isa 在元类中获取方法的实现

现在,我们的重点终于到了, isa 到底是什么?

  • 我们在 Runtime 的源码中可以看到,在不同的处理器上,这个共同体所分配的内存位数是不同的。

    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
    #if SUPPORT_PACKED_ISA
    
        // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
        // nonpointer must be the LSB (fixme or get rid of it)
        // shiftcls must occupy the same bits that a real class pointer would
        // bits + RC_ONE is equivalent to extra_rc + 1
        // RC_HALF is the high bit of extra_rc (i.e. half of its range)
    
        // future expansion:
        // uintptr_t fast_rr : 1;     // no r/r overrides
        // uintptr_t lock : 2;        // lock for atomic property, @synch
        // uintptr_t extraBytes : 1;  // allocated with extra bytes
    
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 19;
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
        };
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 8;
    #       define RC_ONE   (1ULL<<56)
    #       define RC_HALF  (1ULL<<7)
        };
    };
    

    x86_64_ 为例,

    图片来自一位大神的博客,侵权删

  • 更深一步,从 isa 的初始化来看 isa 的结构

    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        initIsa(cls, true, hasCxxDtor);
    }
    
    inline void 
    objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
    { 
        if (!indexed) {
            isa.cls = cls;
        } else {
            isa.bits = ISA_MAGIC_VALUE;
            isa.has_cxx_dtor = hasCxxDtor;
            isa.shiftcls = (uintptr_t)cls >> 3;
        }
    }
    
    // 由于对象的 isa 初始化时传入 indexed 为 true ,所以,可简化为
    inline void 
    objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
    { 
      // 其中 ISA_MAGIC_VALUE 为 0x000001a000000001ULL
            isa.bits = ISA_MAGIC_VALUE;
            isa.has_cxx_dtor = hasCxxDtor;
            isa.shiftcls = (uintptr_t)cls >> 3;
    }
    
    • 此时,当执行完 isa.bits = ISA_MAGIC_VALUE; 后 isa 的结构为 ,可以看到 ISA_MAGIC_VALUE 将 magic 和 indexed 都初始化了

      image
    • 接着 isa.has_cxx_dtor = hasCxxDtor; 这一位会设置 has_cxx_dtor 的值,如果是 1, 则表示当前对象是否有析构器,如果没有,就会快速释放

    • 最后, isa.shiftcls = (uintptr_t)cls >> 3; 将当前对象对应的类指针赋值给 shiftcls 这些位,之所以向右移三位,移三位的主要原因是用于将 Class 指针中无用的后三位清楚减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。赋值之后如下

![image](http://upload-images.jianshu.io/upload_images/3262069-f3a02c34c16975e6?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

至此,也就证明了我们之前对于初始化 `isa` 时对 `initIsa` 方法的分析是正确的。它设置了 `indexed`、`magic` 以及 `shiftcls`。

获取 isa

  • 由于我们现在使用了结构体 isa_t 来替代 Class 类型的指针, 所以我们也就需要一个指针能够返回 isa 所指的类,所以我们此时需要一个 ISA() 方法。

    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);
    #endif
    }
    
    // 简化后如下
    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer());
      // 由此可以看到,实际上 ISA() 返回的是 isa.bits 与 0x0000000ffffffff8ULL 进行的按位与操作,确实可以返回当前的类
        return (Class)(isa.bits & ISA_MASK);
    }
    

总结

  • 至此,此次源码分析的第一部分也就此结束,如果您发现了什么问题和不足欢迎与我探讨和指教。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容