准备
需要使用到编译的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
指向元类
。作用是将实例方法和类方法进行分区存放,实例方法存在于类中,类方法存在于元类中
再次格式化输出LGPerson
得到isa
,然后将isa & ISA_MASK
按位与计算后得到shiftcls 0x000000010034c0f0
,可以得到输出也是NSObject
,然后以16进制打印NSObject.class
后得到地址为0x000000010034c140
,类型为NSObject
的类,此时NSObject
被称为根元类
。从而可以得到如下图中的流程:
- 从图中总结继承流程:继承只存在于类中,子类继承自父类,父类继承自根类(NSObject),NSObject继承自nil,类和元类只存在
isa
指向关系,不存在继承关系,子元类继承自父元类,父元类继承自根元类,根元类继承自NSObject。
类的内存分布
-
objc_object
是C/C++编写的结构体 ,是所有类的最低层的实现,包含了一个isa
属性。 -
objc_class
继承自objc_object
是所有自定义类的基础,在objc_class
中主要有继承自objc_object
的isa
,Class superclass
、cache_t cache
和class_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();
}
}
以上是ISA
和superclass
是容易理解的都是Class
类型,占8字节。主要就是cache
的大小。
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
中只有上面一些数据占用了内存,buckets
为bucket_t
类型其中的_imp
为unsigned 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
中包含了methods
、properties
和protocols
属性,所以可以看出关于类中的属性,方法和协议都在class_rw_t *data()
中。
拓展
objc_object
和对象
的关系
对象是以objc_object
为模板创建而来的,NSObject
为OC层结构,objc_object
为C/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
,然后分别使用数组下标和指针来输出数据,得出如下结果:
因此我们可以看出在输出数组数据时我们既可以使用下标输出,也可以使用指针来输出。在代码中,
*b = a
的含义是指针b
指向了数组a
的首地址,即数组a的第一个元素所在的地址,因此只需要移动指针,就可以得到整个数组的元素。由此可以类推到类中。指针平移.png
当我们使用
p/x
打印类类型和地址,该地址即为首地址,所以我们可以使用内存平移的方式来访问类的内存情况。
网友评论