Runtime学习-类的数据结构

1、Runtime的定义

将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。

Objective-C 语言 是一门动态语言,在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。

Objective-C 语言把一些决定性的工作从编译阶段、链接阶段推迟到运行时阶段,使得 Objective-C 变得更加灵活。这也就说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义。

Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。

Runtime 基本是用 C 和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。

2、与Runtime进行交互

OC支持从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码NSObject类定义的方法Runtime 函数的直接调用。

Objective-C 源代码

使用OC在编写代码的时候,基本都是使用类、属性、方法调用、协议、分类。但是在这些的背后,都是由runtime来支持的。我们平常所熟知的各种类型,背后都有runtime对应的C语言结构体,及C和汇编实现。比如类就被编译成objc_class的结构体,方法被编译成method_t的结构体等等。

NSObject 的方法

Cocoa 中大多数类都继承于 NSObject 类,也就自然继承了它的方法。作为大部分Objective-C类继承体系的根类的NSObject,其本身就具有了一些非常具有运行时动态特性的方法,使用这些方法也算是一种与Runtme的交互方式,举例如下:

//返回对象的类;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
//检查对象对应的类,与指定的类aClass及其父类进行判断。只要有相同的类,就判断为true
- (BOOL)isKindOfClass:(Class)aClass;
//检查对象所在的类,与指定的类aClass,如果一致就返回true
- (BOOL)isMemberOfClass:(Class)aClass;
//用来判断对象是否实现了指定协议类的方法;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; 
//用来判断对象能否响应指定的消息;
- (BOOL)respondsToSelector:(SEL)aSelector; 
2、Runtime 的函数

Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。在文件中引入Runtime的头文件#include <objc/runtime.h>,就可以使用Runtime的方法,并且适合OC程序是一样的效果。举例如下:

   Man * m = [[Man alloc] init];
  //OC语句
  Class mClass = [m class];
  //runtime函数
  Class objClass = object_getClass(m);
  NSLog(@"mClass = %@, objClass = %@",mClass,objClass);
  //打印结果如下
  //2022-05-31 09:40:14.506089+0800 schemeUse[1860:59157] mClass = Man, objClass = Man

3、Runtime中类的数据结构

Objc 2.0之前的版本
objc 2.0.png
Objc 2.0之后的版本
after-objc_2.0.png

由OBJC 2.0后的版本的代码可以看出:

  1. Class类是一个objc_class *指针的别名
  2. objc_class是继承objc_object的结构体
  3. 由于objc_object有一个实例变量isa,并且objc_objectobjc_class的superClass,则在objc_class必然是有isa实例变量。
  4. 在Runtime系统中,对象被定义为一个objc_object类型的结构体,所以Class也被看作是一类特殊的对象。
  5. id是一种objc_object *的指针的别名
4、了解isa

从Objc 2.0以后的源代码可以看出,所有的类都是继承于objc_object,而objc_object中只有一个变量,即 isa_t类型的isa。也就是说Objective-C 对象都是 C 语言结构体,所有的对象都包含一个类型为 isa 的指针。所有与类相关的信息,都是在isa中进行存储,比如说:是否指针优化、是否有关联对象、是否被弱引用等。

那么isa的作用是什么呢?

  1. 方法查询机制。在Object-C中,对象的方法并不存储在各个对象的内存中(如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响),而是保存在类的结构体中(class_ro_t)。当实例对象调用方法的时候,该对象要通过自己的isa找到对应的类,在类的方法列表(class_ro_t)中查找方法的实现。如果类的方法列表中不存在,就是用类的superClass去父类的方法列表继续查找。
    但是如果是类方法的调用,就会有不同。Objective-C为了在调用方法的时候,使得类方法和对象方法保持一致的方法查找机制,就引入了 元类的概念。每一个类的isa都指向自己的元类,在类中定义的类方法,存储在元类中。在类方法的调用过程中,是根据isa查询到自己的元类,在元类的方法列表中,查找类方法实现。
    isa指向

    isa和superClass指向.png

  2. 表明是否有关联对象(has_assoc)。

  3. 表明是否有弱应用(weakly_referenced)。

  4. 对象的引用计数信息(extra_rc)。

  5. 存储对象的类的地址信息(shiftcls)。

isa的数据结构:

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)
  };

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif


#if SUPPORT_INDEXED_ISA

# if  __ARM_ARCH_7K__ >= 2

#   define ISA_INDEX_IS_NPI      1
#   define ISA_INDEX_MASK        0x0001FFFC
#   define ISA_INDEX_SHIFT       2
#   define ISA_INDEX_BITS        15
#   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
#   define ISA_INDEX_MAGIC_MASK  0x001E0001
#   define ISA_INDEX_MAGIC_VALUE 0x001C0001
  struct {
      uintptr_t nonpointer        : 1;
      uintptr_t has_assoc         : 1;
      uintptr_t indexcls          : 15;
      uintptr_t magic             : 4;
      uintptr_t has_cxx_dtor      : 1;
      uintptr_t weakly_referenced : 1;
      uintptr_t deallocating      : 1;
      uintptr_t has_sidetable_rc  : 1;
      uintptr_t extra_rc          : 7;
#       define RC_ONE   (1ULL<<25)
#       define RC_HALF  (1ULL<<6)
  };

# else
#   error unknown architecture for indexed isa
# endif

// SUPPORT_INDEXED_ISA
#endif

};

可以看出isa_t 主要是有Class cls 和不同系统下的结构体组成。在不同的系统下 ,结构体的参数是一样的,只是空间分配有所不同。这样的话。这里以"__arm64__“进行展示:

union isa_t
{
  isa_t() { }
  isa_t(uintptr_t value) : bits(value) { }

  Class cls;
  uintptr_t bits;
  
  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;
  };
};

isa_t是一个共用体union
union:在一个“联合”内可以定义多种不同的数据类型, 一个该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。这种“union”只能存放一种数据,在isa_t中cls和结构体只能使用一个变量。

isa字段.png

示意图


arm64.png

nonpointer

自2013年苹果推出iphone5s之后,iOS的寻址空间扩大到了64位。我们可以用63位来表示一个数字(一位做符号位)。那么这个数字的范围是2^63 ,很明显我们一般不会用到这么大的数字,那么在我们定义一个数字时NSNumber *num = @100,实际上内存中浪费了很多的内存空间。

当然苹果肯定也认识到了这个问题,于是就引入了tagged pointer。tagged pointer是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。
苹果对于Tagged Pointer特点的介绍:


taggedpointer.jpg
  • Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString。
  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
  • 在内存读取上有着3倍的效率,创建时比以前快106倍。


    tagged pointer.png

如上面tagged pointer所说,如果isa指针仅表示类型的话,对内存显然也是一个极大的浪费。于是,就像tagged pointer一样,对于isa指针,苹果同样进行了优化。isa指针表示的内容变得更为丰富,除了表明对象属于哪个类之外,还附加了引用计数extra_rc,是否有被weak引用标志位weakly_referenced,是否有附加对象标志位has_assoc等信息。

isa_t其实可以看做有两个可能的取值,Class cls或struct。isa作为Class cls使用的时候,是我们一贯的认知,存储对应的地址。这样的话,内存是有比较大的浪费。所以绝大多数情况下,苹果采用了优化的isa策略,即,isa_t类型并不等同于Class cls, 而是struct。这在初始化isa的时候,表现很明显。

有关联对象标志位has_assoc

  NSObject * obj = [[NSObject alloc] init];
  NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
   //2022-06-07 09:50:26.535944+0800 schemeUse[2530:174823] isa_t = 0x21a1dfe7f289
  objc_setAssociatedObject(obj, "xxxx", self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  NSLog(@"isa_t-associate = %p", *(void **)(__bridge void*)obj);
  //2022-06-07 09:50:26.536051+0800 schemeUse[2530:174823] isa_t-associate = 0x21a1dfe7f28b

可以看出,添加关联对象以后,isa是有变化的,全部转成二进制数据进行对比:

添加关联对象.jpg

对比上面“arm64”的示意图,可以比较出来,第二位数据变成了“1”,也就是 has_assoc位变化了。

被弱引用标志位weakly_referenced

    NSObject * obj = [[NSObject alloc] init];
    NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
    //2022-06-07 10:13:45.789576+0800 schemeUse[2647:180952] isa_t = 0x21a1dfe7f289
    __weak typeof(obj) weakObj = obj;
    NSLog(@"isa_t-weak = %p", *(void **)(__bridge void*)obj);
    //2022-06-07 10:13:45.789685+0800 schemeUse[2647:180952] isa_t-weak = 0x25a1dfe7f289

全部转成二进制数据进行对比,可以看出第42位weakly_referenced位被置位1:

weak引用.jpg

isa中的引用计数extra_rc
代码:

  NSObject * obj = [[NSObject alloc] init];
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t—1 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495684+0800 schemeUse[6396:483268] refCount = 1
  //2022-06-08 10:56:14.495792+0800 schemeUse[6396:483268] isa_t-refrence-1 = 0x21a1dfe7f289

  NSObject * obj_refrence1 = obj;
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t-refrence-2 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495831+0800 schemeUse[6396:483268] refCount = 2
  //2022-06-08 10:56:14.495864+0800 schemeUse[6396:483268] isa_t-refrence-2 = 0x41a1dfe7f289

  NSObject * obj_refrence2 = obj;
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t-refrence-3 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495897+0800 schemeUse[6396:483268] refCount = 3
  //2022-06-08 10:56:14.495928+0800 schemeUse[6396:483268] isa_t-refrence-3 = 0x61a1dfe7f289

全部转成二进制数据进行对比,可以看出extra_rc的19位数据的变化(需要指出的是,如果has_sidetable_rc的标识位为1,那么也就意味着extra_rc的19位二进制数据装不下对象的引用计数,也就保存到了sidetable一部分。这样的话引用计数就是两部分加起来。):

引用计数.jpg

对象的实例化过程中对isa的创建过程

1、类调用alloc方法时,runtime系统内部会调用_objc_rootAlloc(self);
2、而 _objc_rootAlloc方法中只是调用了callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);;
3、在callAlloc方法中,分配对象的空间以及初始化isa(initInstanceIsa(cls, dtor)方法)。

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
       //其他代码xxxxxxxx
       bool dtor = cls->hasCxxDtor();
       id obj = (id)calloc(1, cls->bits.fastInstanceSize());
       if (slowpath(!obj)) return callBadAllocHandler(cls);
       obj->initInstanceIsa(cls, dtor);
       return obj;
       //其他代码zxxxxxxxxxx
}

4、initInstanceIsa 最终调用的是objc_object类的inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)方法

inline void  objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
     //nonpointer的值,在initInstanceIsa方法中默认传值为true。initIsa(cls, true, hasCxxDtor);
  if (!nonpointer) {
      //如果没没有使用isa优化,使用isa结构体中的cls,来接受对象对应的类信息。
      isa.cls = cls;
  } else {
      //使用了isa优化,创建一个新的isa,处理相关的信息,然后赋值给对象的isa
       //对象实例化的时候,需要处理的isa信息包括:has_cxx_dtor、shiftcls等
      isa_t newisa(0);
      /**其他代码**/
      newisa.bits = ISA_MAGIC_VALUE;
      newisa.has_cxx_dtor = hasCxxDtor;
      newisa.shiftcls = (uintptr_t)cls >> 3;
      isa = newisa;
  }
}
对象的销毁过程中,isa的作用

1、对象销毁,会调用dealloc方法,该方法内部调用的是_objc_rootDealloc(self);
2、_objc_rootDealloc方法内部,调用起了obj->rootDealloc();
3、 rootDealloc方法中,主要是对isa的信息进行判断,检查是否可以直接free对象

inline void
objc_object::rootDealloc()
{
  if (isTaggedPointer()) return;  // fixme necessary?

  if (fastpath(isa.nonpointer  &&     //对象的isa是有优化的
               !isa.weakly_referenced  &&   //对象没有被弱引用
               !isa.has_assoc  &&   // 不存在关联对象
               !isa.has_cxx_dtor  &&   // 没有 有C++的析构函数
               !isa.has_sidetable_rc))  //在sidetable中不存在引用计数
  {
      assert(!sidetable_present());
      //直接free
      free(this);
  } 
  else {
      //需要去处理isa中相关的信息
      object_dispose((id)this);
  }
}

4、在 object_dispose方法中,调用objc_destructInstance(obj),清理isa中的相关信息,弱引用、关联对象、c++析构函数、引用计数等,清理完成以后,释放对象。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();   //获取isa中C++标志位的信息
        bool assoc = obj->hasAssociatedObjects(); //获取isa中关联对象的信息

        // This order is important.
        if (cxx) object_cxxDestruct(obj); //C++标志位有数据,处理析构
        if (assoc) _object_remove_assocations(obj);  // 清理关联的对象
        obj->clearDeallocating(); //清理弱引用、sidetable的引用计数
    }

    return obj;
}
//清理弱引用、sidetable的引用计数
inline void  objc_object::clearDeallocating()
{
  if (slowpath(!isa.nonpointer)) {
      // Slow path for raw pointer isa.
      sidetable_clearDeallocating();
  }
  else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
      // Slow path for non-pointer isa with weak refs and/or side table data.
      clearDeallocating_slow();
  }

  assert(!sidetable_present());
}

5、类的结构

在runtime系统中,objc_class是类的结构体,objc_class继承于objc_object

struct objc_class : objc_object {
    // Class ISA;
    Class superclass; //父类
    cache_t cache;             // formerly cache pointer and vtable // 方法缓存
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags //存储类的信息,成员变量,方法,协议等

  //可以使用data通过一些与或操作,获取到成员变量、方法、协议等
    class_rw_t *data() {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    //初始化,以及获取isa、类的信息的一些方法
    ///xxxxxxxx

};

可以看出,在objc_class中,有三个数据:Class superclasscache_t cacheclass_data_bits_t bits

Class superclass表明当前类的父类,其本身也是一个Class类型。
  1. 根元类的父类,是NSObject
  2. NSObject的父类是nil。
cache_t cache cache的作用在于缓存方法, 方法调用的时候,可以更快速的找到方法的IMP

cache的数据结构为:

//方法缓存的结构,共占用16个字节
struct cache_t {
private:
 //8个字节,存放方法的空间首地址信息(low 48位)和mask信息(high 16位),
 explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
 
 union {
     struct {
         //_maybeMask is the buckets mask
         explicit_atomic<mask_t>    _maybeMask; //4个字节,存储的时候是newCapacity - 1
#if __LP64__
         uint16_t                   _flags;
#endif
         //缓存的方法数量
         uint16_t                   _occupied;
     };
     explicit_atomic<preopt_cache_t *> _originalPreoptCache;
 };

 //向缓存中添加方法
 void insert(SEL sel, IMP imp, id receiver);
 
 //取缓存方法列表
 struct bucket_t *buckets() const;
 
 //可以缓存方法的空间数量
 unsigned capacity() const;
 
 //已经缓存的方法数量
 mask_t occupied() const;
 
 //增加已缓存方法的数量
 void incrementOccupied();
 
 //设置_bucketsAndMayBeMask
 void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);

 //开辟缓存的空间
 void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);

 /**其他方法**/
};

cahce_t是一个结构体,共计16个字节的大小。包含两个成员,一个是_bucketsAndMaybeMask,一个是union联合体,各占8个字节。cache_t中的方法主要围绕这两个成员的操作进行的。

_bucketsAndMaybeMask:实际上是一块内存的首地址。方法缓存主要是以buckets(bucket_t)进行存储,存储内容就是SELIMP_bucketsAndMaybeMask通过和 mask的与操作,就能获得缓存的方法列表。

  struct bucket_t *cache_t::buckets() const
  {
     uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
     return (bucket_t *)(addr & bucketsMask);
   }

方法的缓存过程:
方法缓存调用的是void insert(SEL sel, IMP imp, id receiver);方法。该方法要做的事情分为三块:

  • 开辟缓存空间
  • 缓存空间的扩容
  • 方法插入缓存


void cache_t::insert(SEL sel, IMP imp, id receiver)
{
   
    //1.开辟缓存空间
    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        //如果缓存里面为空。先分配4个容量大小的空间。并调用reallocate去开辟空间
        if (!capacity) capacity = INIT_CACHE_SIZE; // 1<<2
        //开辟空间,并且给_bucketsAndMayBemask、_maybemask赋值
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    //2、缓存空间的扩容
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    }
#endif
    else {
        
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

    
    
    //3、方法插入缓存,方法插入缓存的代码
    bucket_t *b = buckets();
    mask_t m = capacity - 1;//3
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();// _occupied++;
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

消息机制中,方法的缓存流程:
消息机制的缓存
class_data_bits_t bits: 保存类的成员变量、成员方法、协议、属性

在之前isa的学习中,讨论过对象的方法不存储在各个对象的内存中,而是保存在类的结构体中。具体保存类结构的什么位置呢?就是class_data_bits_t bits。使用该成员可以获取到一个类的成员变量、成员方法、协议、属性等信息。
class_data_bits_t数据结构如下:

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;

public:
    //类的成员变量、成员方法、协议、属性(包含分类创建的方法)
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
  //类的成员变量、成员方法、协议、属性(只是在类中定义的,并不包含分类中的方法)
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
    
    /*其他方法*/

};

这个结构体是一个64bit的成员变量bits,先来看看这64bit分别存放的什么信息:

class_bits_t.png

可以看出数据的第4-48位,是存放一个指向class_rw_t结构体的指针,该结构体包含了该类的属性,方法,协议等信息,是class_data_bits_t bits的核心。
下面就来说说核心部分:class_rw_tclass_ro_t
rw和ro.png

class_ro_t说明:
1、该属性是在编译期就生成的,它存储的内容包含了当前类的名称,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 这些类的基本信息,在class_ro_t里面是没有分类中定义的方法和协议的。

class_rw_t说明:
1、是class_ro_t外面的一层,在运行时生成的。在_objc_init方法中关于dyld的回调的map_images中,调用realizeClass方法,该方法最终将分类的方法与协议都插入到自己的方法列表、协议列表中。
2、它不包含成员变量列表,因为成员变量列表是在编译期就确定好的,它只保存在class_ro_t中。
3、成员方法会保存在class_rw_t中。
4、class_rw_t中包含了一个指向class_ro_t的指针,可以方便的读取类的成员变量信息。

在调用realizeClass方法之前,类的 class_data_bits_t是指向class_ro_t结构体;调用realizeClass方法之后,class_data_bits_t是指向class_rw_t结构体,把分类中的方法汇总到class_rw_t中。并且class_rw_t会有一个变量指向class_ro_t

class_bits_t的指向.png

6、类的总结图示

类的大小:


runtime_image.jpg

类的结构总结图:


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

推荐阅读更多精彩内容