我们都知道,一个类可以创建多个不同实例对象,类自己也是对象(类对象),那么类在内存中会存在几份呢?看下面结果
Class class1 = [MGPerson alloc].class;
Class class2 = [MGPerson alloc].class;
Class class3 = [MGPerson class];
Class class4 = object_getClass([MGPerson alloc]);
得出的结果是 class1
、class2
、class3
、class4
的地址一样
也就是类对象并不像实例对象一样在内存中有多份存在,只占用一份内存。
总结:类(类对象)
在内存中只有一份存在;
类的内存地址分析
通过控制台输出类的地址是 0x0000000100002330
(lldb) p/x MGPerson.class
(Class) $0 = 0x0000000100002330 MGPerson
然而当我们用类的isa
& 0x7ffffffffff8
得到地址0x0000000100002308
, po输出也是MGPerson
, 可以通过上一节isa 和 类的关系查看isa分析
看到这里会发现和前面的分析结论类在内存中只有一份
相矛盾,真的是这样的吗?答案是否定的,看到的0x0000000100002308
并不是类的内存地址,而是元类
, 是MGPerson中的isa指向的元类。
元类的说明:
-
元类
是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自元类。 -
元类
本身没有名称的, 由于与类相关联,所以使用了同类名一样 的名称。
接着分析MGPerson类对象
的元类的地址,输出内存地址为:
(lldb) x/4gx 0x0000000100002308
0x100002308: 0x00007fff8e0c50f0 0x00007fff8e0c50f0
0x100002318: 0x00000001007040c0 0x0001e03500000007
继续元类的isa
& 0x7ffffffffff8
得到地址0x00007fff8e0c50f0
, po输出是NSObject
元类的isa
指向根元类
, 那么根元类
的isa
指向谁? 继续输出 0x00007fff8e0c50f0
指向的地址
(lldb) x/4gx 0x00007fff8e0c50f0
0x7fff8e0c50f0: 0x00007fff8e0c50f0 0x00007fff8e0c5118
0x7fff8e0c5100: 0x00000001005153c0 0x0004e03100000007
image.png
可以发现根元类
的isa
指向的还是自己,即 0x00007fff8e0c50f0。
至此可以得出实例对象
、类
、元类
、根元类
的关系图如下:
总结:
-
实例对象(Instance of Subclass)
的 isa 指向类(class)
-
类对象(class)
isa 指向元类(Meta class)
-
元类(Meta class)
的isa 指向根元类(Root metal class)
-
根元类(Root metal class)
的isa 指向自己
,形成闭环,这里的根元类就是NSObject
类(Class)
我们定义的对象都是继承于NSObject
对象,可以通过isa 和 类的关系中找到类的结构体(objc_class)定义。
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
void clearInfo(uint32_t clear) {
ASSERT(isFuture() || isRealized());
data()->clearFlags(clear);
}
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
ASSERT(isFuture() || isRealized());
ASSERT((set & clear) == 0);
data()->changeFlags(set, clear);
}
...
}
可以看出 objc_class
继承于 objc_object
, 发现有一个isa
变量,可以得出所有的对象都有一个isa
指针。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
...
}
总结:
- 所有的
对象
,类
,元类
都有isa
变量 - 所有的
对象
都继承于objc_object
类结构分析
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// 获取bits存放的数据信息
class_rw_t *data() const {
return bits.data();
}
...
}
通过上面分析可以看出类(class)中有4个属性分别是:
-
isa
: 继承自objc_object
的isa
,占用8个字节; -
superclass
: Class类型, 也是objc_class
结构体,是一个结构体指针,占用8个字节; -
cache
: 存放缓存的指针,以及缓存的函数,占用16字节; -
bits
: 类型为 class_data_bits_t ,存储了类中更详细的信息(例如:属性,方法,协议);
【cache_t类型】内存大小 16字节
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
...
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
....
}
class_data_bits_t结构分析
通过上面分析类的结构可知 类内存地址(首地址)
平移32字节(即十六进制0x20)即可查找到 bits
步骤解析:
- 获取类的首地址
0x00000001000013d8
- 获取类的首地址
- 通过首地址偏移32字节(即0x20)得到
bits
的内存地址
- 通过首地址偏移32字节(即0x20)得到
- 强转成
class_data_bits_t
类型, 读取bits.data()
存放的数据信息,即$3;
- 强转成
- 打印输出
$3
的内容 (class_rw_t) $4, 类型是class_rw_t,也是一个结构体类型;
- 打印输出
class_rw_t 结构
在64位架构CPU下,bits 的第3到第46字节存储class_rw_t
。class_rw_t 中存储 flags 、witness、firstSubclass、nextSiblingClass, methods, properties, protocols 以及class_rw_ext_t
。
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
class_rw_ext_t
中存储着class_ro_t
和 methods (方法列表)
、properties (属性列表)
、protocols (协议列表)
等信息。
而 class_ro_t
中也存储了baseMethodList (方法列表)
、baseProperties (属性列表)
、baseProtocols (协议列表)
以及 实例变量、类的名称、大小 等等信息。
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;
};
通过源码获取属性列表、方法列表、协议列表等
image.png-
通过获取属性列表
property_array_t
可以拿到属性
image.png
注:p *($6 + 1)
,p *($6 + 2)
可以获得属性列表的中的下一个数据
或者通过$7.get()
获取各个属性
注意:properties()只能获取到属性列表, 如果成员变量并不在这里面存放着,而是存放在
ivars
成员变量列表中。
-
通过函数methods()获取
method_array_t
获取方法列表
步骤解析:
-
p $4.methods()
获取方法列表list, 即得到的$6
-
p *$6
拿到方法列表中的第一个方法,即 ‘sayHello’ -
p $7.get(1)
、p $7.get(2)
等是获取方法列表中的其他方法。
网友评论