[ios] Runtime source code reading and sharing the nature of the object, understand isa

introduction

We all know that Runtime is the core of the dynamic language Objective-C. Only by understanding it can we better understand how Objective-C works, and will be more handy when programming. Due to the limited time and energy, this time I mainly want to read the Runtime source code from the following aspects, which will be gradually improved in the future. Because the overall length is long, I will split each part into an article for specific analysis.

content

1. The nature of the object, understand isa

The life cycle of an object

3. Object reference count

Fourth, the extension method of the object

5. Application of Runtime

The nature of objects, understanding isa

First look at the definitions of objects and classes

/// 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();
}; // There is a pointer to Class inside, and what is Class?// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; // Let's look at the definition of objc_class again, it should be noted that , objc_class is also inherited from objc_object. Since objc_object has defined an isa pointer, and since all members of the structure are public, objc_class also has isa and also has access to isa. struct objc_class : objc_object { // Class ISA; // At this point, the isa pointer in the class points to the metaClass metaclass Class superclass; // superclass cache_t cache; // formerly cache pointer and vtable class method cache, because Runtime When the method is encountered for the first time, it will be cached in the method cache, and then the method will be directly read from the cache, which greatly improves the efficiency class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags // class_data_bits_t class_rw_t + one rr/alloc bits};

So here, we also understand that, in fact, a class is also an object

  • What we need to know is that in Objective-C, the methods of objects are not stored in the structure of the object (if each object saves methods that it can execute, it will have a great impact on memory usage). When calling an instance method, the corresponding class is found through the isa pointer, and the method in the class is found through class_data_bits_t. How to find it, let's see

    // 首先 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           (1data is class_rw_t, not class_ro_t
              #define RW_REALIZED           (1setData(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 中,实际上类也是一个特殊的对象,查找类的方法实际上就和查找实例方法采用同样的机制,但是如何才能让他们采用同样的机制呢?这时,元类的作用就显现了出来。

    • 当实例方法调用时,通过对象的isa在类中获取方法的实现

    • 当类方法调用时,通过类的isa在元类中获取方法的实现

    • metaClass 保证了类中也有一个指向 Class 类型的指针,保证了类和对象的一致性,保证了类查找方法的机制与对象查找方法的机制保持同步。

现在,我们的重点终于到了, 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);
    }

总结

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324892489&siteId=291194637