- 前面几篇文章,我们探索了对象的本质:
- 明白了对象的本质后,你会不会有这样的疑问:
- 类的本质是什么?
- 类的结构是什么样的?
- 我们平时使用的属性和方法是存储在什么地方?
- 带着这些问题,我们一起探索下类的本质。
类的结构
- 首先看下面代码的底层实现:
@interface LGPerson : NSObject
@property (nonatomic) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
NSLog(@"p:%@", p);
}
return 0;
}
- 使用
clang
编译:clang -rewrite-objc main.m -o main.cpp
// ...
struct objc_class;
typedef struct objc_class *Class;
// ...
struct NSObject_IMPL {
Class isa;
};
// ...
typedef struct objc_object LGPerson;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// ...
- 从编译后的结果可以看出:(这里省略了和本文分析无关的代码)
LGPerson
类实际上是struct objc_object
类型LGPerson
的具体实现是LGPerson_IMPL
,第一个成员是NSObject_IMPL
中的isa
isa
的类型是Class
,Class
的类型是struct objc_class
objc_class
在这里只有声明,并没有具体定义,我们可以看下objc
源码中是否有相关定义
objc
源码objc_class
定义:
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
// ...
}
struct objc_object {
isa_t isa;
}
- 从
objc
源码可以看到,objc_class
继承自objc_object
,第一个成员变量是isa
- 在前面的文章我们分析过了对象的
isa
指向类,那么类的isa指向什么呢?
类的isa
- 通过下面的代码,看下类的isa指向
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
}
return 0;
}
LLDB
打印isa
信息:x/4gx
:打印内存中存储的数据p/x
:以16进制形式输出0x00007ffffffffff8
:是ISA_MASK
的值,&
该值的目的是取出NONPOINTER_ISA
中的isa
值
- 从上面的打印结果可以看出
- 实例变量的
isa
指向类LGPerson
LGPerson
类的isa
指向另一个LGPerson
类(这两个LGPerson
类不是同一个,因为他们的地址不同,实际上第二个LGPerson
类是元类)- 打印
LGPerson
类的内存地址,通过此验证得知,对象的isa
指向类,类的isa
指向元类
- 实例变量的
类的关系
- 基本概念
- 根类:在
OC
中几乎所有的类都继承自NSObject
类,NSObject
类就是根类,根类的父类为nil
- 元类:在我们平时开发中会用到类方法和实例方法,但是在底层的实现中并没有这种区分,实际上都是通过实例方法去查找的,底层为了区分这两种方法,引入元类的概念,然后将实例方法存储在类中,类方法存储在元类中。类的
isa
指向元类。 - 根元类:即为根类
NSObject
的isa
指向的类
- 根类:在
- 关系图
- 总结:
isa
的指向:- 实例变量的
isa
指向对应的类 - 类的
isa
指向对应的元类 - 元类的
isa
指向根元类 - 根元类的
isa
指向自身
- 实例变量的
- 类的继承:
- 类的
superclass
指向父类 - 父类的
superclass
指向根类 - 根类的
superclass
指向nil
- 类的
- 元类的继承:
- 元类的
superclass
指向对应类的父类的元类 - 父类的元类的
superclass
指向根元类 - 根元类的
superclass
指向根类 - 根类的
superclass
指向nil
- 元类的
补充:结构体内存平移取值
- 举例:
struct LGStruct {
int a; // 偏移:0, 占4字节
char b; // 偏移:4, 占1字节
int c; // 偏移:8, 占4字节
long d; // 偏移:16,占8字节(成员变量的开始位置为当前成员变量所占内存的整数倍)
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct LGStruct s;
s.a = 10;
s.b = 'a';
s.c = 30;
s.d = 40;
}
return 0;
}
- 取出某个成员变量的值:
- 先确定结构体变量地址
- 确定成员变量的偏移位置
- 计算成员变量的内存地址:结构体变量地址+该变量偏移位置
- 将计算得到的地址转为该变量对应的类型的指针类型(例如
a
是int
类型,则将计算的地址转为int *
类型,这样转的目的是取出int
类型的值)
类的存储
- 通过前面的分析,我们对类之间的关系和类的结构有了一个大致的了解,但是我们还不是很清楚类在内存中具体是如何存储的?
- 属性存储在哪里?
- 成员变量存储在哪里?
- 方法存储在哪里?
- 下面我们通过
LLDB
结合源码的方式分析下类在内存中的存储
类的bits分析
- 以下的调试分析需要用到
objc
源码工程,本文调试的源码地址:gitee.com/jrcode/ios_… LGPerson
定义:
@interface LGPerson : NSObject {
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)say666;
@end
@implementation LGPerson
- (void)sayHello {}
+ (void)say666 {}
@end
- 从上面类的结构可以看出,类中主要有4个成员变量:
Class ISA;
:isa指针Class superclass;
:父类cache_t cache;
:缓存相关class_data_bits_t bits;
- 从类的成员变量可以看出,类中的数据应该都存储在
bits
中,下面重点分析下bits
- 首先看下
cache_t
的定义:
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4字节(uint32_t)
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
// 静态变量和方法是不占用内存的
}
- 从
cache_t
的定义可以看出:- 第一个成员变量
_bucketsAndMaybeMask
为uintptr_t
类型,实际为unsigned long
类型,占8
字节 - 第二个成员变量为联合体(联合体成员共享同一块内存)
- 联合体的第一个成员为结构体,最大占
8
字节 - 联合体的第二个成员为指针类型,占
8
字节 - 所以,联合体占内存大小即为最大成员占内存大小,即为
8
字节
- 联合体的第一个成员为结构体,最大占
- 所以,
cache_t
占内存为16
字节
- 第一个成员变量
bits
数据的获取:- 通过前面介绍的结构体内存平移取值的方法可以获取
bits
的值 - 在获取
bits
的值之前需要先确定bits
的偏移量 - 前两个成员变量
ISA
和superclass
都是Class
类型,而Class
为structobjc_class *
类型,占8字节 - 第三个成员变量
cache
为cache_t
类型,占16
字节 - 所以,成员变量
bits
的偏移地址为32
字节(可以通过结构体内存地址+0x20
计算得到)
- 通过前面介绍的结构体内存平移取值的方法可以获取
class_data_bits_t
定义:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// ...
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...
};
- 从
class_data_bits_t
的定义可以看出,可以通过data()
获取数据,结果为class_rw_t
类型 class_rw_t
的定义:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// ...
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
// ...
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
// ...
};
- 从
class_rw_t
的定义可以看出几个重要的方法:methods()
properties()
ro()
- 下面通过
LLDB
调试验证一下,这里是不是和我们猜想的一样存放的是对应的数据
读取class_rw_t数据
- 去取
LGPerson
类在内存中的地址 - 通过结构体内存平移获取
bits
值 - 获取
bits
中存储的class_rw_t
类型数据地址 - 查看
class_rw_t
中存储的具体数据
属性的存储(property_t)
p $2->properties()
:获取属性信息,返回property_array_t
类型数据,继承自list_array_tt
:
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
// ...
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
// ...
};
p $8.list.ptr
:获取property_array_t
中存储的property_list_t
数据- 从上面的
list_array_tt
的定义可以看出,这里的list
为List
类型数据,即为传入的property_list_t
类型数据
- 从上面的
- 查看
property_list_t
存储数据,property_list_t
继承自entsize_list_tt
:- 这里定义了一个
get
方法,调用get
最终会调用getOrEnd
,返回的数据类型为Element
Element
的类型即为传入的property_t
类型
- 这里定义了一个
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
// ...
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
// ...
};
- 通过
get
方法获取property_t
类型数据:
struct property_t {
const char *name;
const char *attributes;
};
对象方法的存储(method_t)
p $2->methods()
:获取方法信息,返回method_array_t
类型数据,继承自list_array_tt
p $12.list.ptr
:获取method_array_t
中存储的method_list_t
数据- 查看
method_list_t
存储数据,method_list_t
继承自entsize_list_tt
:Element
的类型即为传入的method_t
类型
- 通过
get
方法获取method_t
类型数据,这里打印{}
,可以看下method_t
定义:- 通过定义可以看出,
method_t
需要通过big()
取出数据
- 通过定义可以看出,
struct method_t {
// ...
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
// ...
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
// ...
};
- 分别打印
sayHello
和属性name
对应的getter
和setter
方法信息
成员变量的存储(ivar_t)
p $2->ro()
:获取ro
信息,返回class_ro_t
类型数据,class_ro_t
定义:- 此结构体中包含
ivar_list_t
类型的成员变量ivars
ivars
中存储我们定义的成员变量数据
- 此结构体中包含
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// ...
};
p $19->ivars
:获取成员变量信息,返回ivar_list_t
类型数据- 查看
ivar_list_t
存储数据,ivar_list_t
继承自entsize_list_tt
:Element
的类型即为传入的ivar_t
类型
- 通过
get
方法获取ivar_t
类型数据:
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
// ...
};
类方法的存储(method_t)
- 类方法存储在元类中
- 通过
x/4gx LGPerson.class
:查看LGPerson
类的内存结构 - 通过
ISA_MASK
的值获取类LGPerson
类的isa
,即找到LGPerson
类对应的元类 - 通过元类的地址,经过内存平移得到元类的
bits
内存地址 - 通过
data()
获取bits
中存储的数据
- 通过
methods()
获取元类的bits
中存储的方法列表数据 p $12.list.ptr
:获取method_array_t
中存储的method_list_t
数据- 查看
method_list_t
存储数据,method_list_t
继承自entsize_list_tt
:Element
的类型即为传入的method_t
类型
- 通过
get
方法和big()
获取method_t
类型数据,输出say666
方法信息,即类方法存储在元类中