美文网首页
isa补充及类的内存分布

isa补充及类的内存分布

作者: 开发狗 | 来源:发表于2020-10-22 23:16 被阅读0次

准备

需要使用到编译的objc源码,objc4可下载,编辑可执行程序可参考最新macOS 10.15下objc4-779.1源码编译调试。以下是基于Mac OS平台运行,ISA_BITFIELD为在x86架构下进行取值。

isa指向

创建一个LGPerson类的实例化对象person,以16进制打印person地址,0x0000000100753450是当前对象的地址,x/4gx person 格式化输出person对象的内存,我们知道首地址为isa,将isa 的地址和ISA_MASK 按位与运算,可得到0x00000001000080e8地址,po该地址得到输出为LGPerson,从而得到ISA_MASK 0x00007ffffffffff8ULL的作用是舍去低三位和高17位,从而获取中间的44位即shiftcls,在之前我们知道shiftcls是存储了类指针的值。再次16进制输出LGPerson类自身时得到地址0x00000001000080e8,由此可知对象的isa指向是类。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
    }
    return 0;
}

person.png LGPerson.png

再次格式化输出LGPerson得到isa,然后将isa & ISA_MASK 按位与计算后得到shiftcls 0x00000001000080c0,可以得到输出也是LGPerson,从而可以得到此时LGPerson为元类。

  • 元类的定义和创建是由编译器自动完成,实例对象的isa指向类对象,类对象isa指向元类。作用是将实例方法和类方法进行分区存放,实例方法存在于类中,类方法存在于元类中
NSObject.png

再次格式化输出LGPerson得到isa,然后将isa & ISA_MASK 按位与计算后得到shiftcls 0x000000010034c0f0,可以得到输出也是NSObject,然后以16进制打印NSObject.class后得到地址为0x000000010034c140,类型为NSObject的类,此时NSObject被称为根元类。从而可以得到如下图中的流程:

isa及继承流程.png
  • 从图中总结继承流程:继承只存在于类中,子类继承自父类,父类继承自根类(NSObject),NSObject继承自nil,类和元类只存在isa指向关系,不存在继承关系,子元类继承自父元类,父元类继承自根元类,根元类继承自NSObject。

类的内存分布

  • objc_object 是C/C++编写的结构体 ,是所有类的最低层的实现,包含了一个isa属性。
  • objc_class继承自objc_object是所有自定义类的基础,在objc_class中主要有继承自objc_objectisaClass superclasscache_t cacheclass_data_bits_t bits,类中的属性,实例方法和协议都被保存在bits

对于类的属性我们可以使用内存平移的方式进行访问.

struct objc_class : objc_object {
    //  Class ISA;        8字节
    Class superclass;        8字节
    cache_t cache;        16字节
    class_data_bits_t bits; 
    class_rw_t *data()  const {
        return bits.data();
    }
}

以上是ISAsuperclass是容易理解的都是Class类型,占8字节。主要就是cache的大小。

objc_class.png
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

由于static静态类型数据不再类中,所以cache_t中只有上面一些数据占用了内存,bucketsbucket_t类型其中的_impunsigned long uintptr_t占用8字节,(unsigned int uint32_t)mask_t类型的_mask。而类中的属性,实例方法和协议在class_rw_t中,如下代码:

struct class_rw_t {

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
}

从而得到class_rw_t中包含了methodspropertiesprotocols属性,所以可以看出关于类中的属性,方法和协议都在class_rw_t *data()中。

拓展

objc_object对象的关系

对象是以objc_object为模板创建而来的,NSObject为OC层结构,objc_objectC/C++层的结构体。

指针平移
 int a[4] = {1, 2, 3, 4};
  int *b = a;
  for (int i = 0; i < 4; i++) {
      NSLog(@"索引取值:%d", a[i]);
      NSLog(@"指针取值:%d", *(b+i));
  }

声明一个int数组a,声明一个指针b指向数组a,然后分别使用数组下标和指针来输出数据,得出如下结果:

指针运算.png
因此我们可以看出在输出数组数据时我们既可以使用下标输出,也可以使用指针来输出。在代码中,*b = a的含义是指针b指向了数组a的首地址,即数组a的第一个元素所在的地址,因此只需要移动指针,就可以得到整个数组的元素。由此可以类推到类中。
指针平移.png
当我们使用p/x 打印类类型和地址,该地址即为首地址,所以我们可以使用内存平移的方式来访问类的内存情况。

相关文章

网友评论

      本文标题:isa补充及类的内存分布

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