类的本质
探索C++源码
环境:xcode 11.5
源码:objc4-781
本篇文章主要是对于类结构进行探索。首先我们创建一个自定义的类。
ZHYPerson.h
@interface ZHYPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@ - %p",person,pClass);
}
return 0;
}
我们利用clang将main.m转换成cpp文件。相关命令为:
clang -rewrite-objc main.m -o main.cpp
打开对应的cpp文件,寻找ZHYPerson相关的代码段
typedef struct objc_class *Class;
...
typedef struct objc_object ZHYPerson;
typedef struct {} _objc_exc_ZHYPerson;
#endif
struct ZHYPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *hobby;
};
struct NSObject_IMPL {
Class isa;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
ZHYPerson *person = ((ZHYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZHYPerson"), sel_registerName("alloc"));
Class pClass = object_getClass(person);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zh_w27k77q94sdgr84dr5dnbqgm0000gn_T_main_1df9cf_mi_9,person,pClass);
}
return 0;
}
...
通过上述代码可以看到:
ZHYPerson本质上是一个objc_object的结构体,它和NSObject的关联在于ZHYPerson_IMPL结构体中存在一个NSObject_IMPL类型的结构体NSObject_IVARS,其内部有一个变量为isa,可以将ZHYPerson与NSObject联系起来。Class是一个指向objc_class的指针。
objc_object
首先来看objc_object的定义
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;
...一些函数...
}
objc_object结构体中包含了一个isa_t的联合体,关于isa_t在isa的初始化&指向分析一文中有相关的探索。
NSObject和objc_object的关系
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
NSObject的内部也存在一个isa,二者是同一事物的不同表现形式。
isa_t和Class isa的关系
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
(lldb) p/t 0x00007ffffffffff8ULL
(unsigned long long) $0 = 0b0000000000000000011111111111111111111111111111111111111111111000
在__x86_64__架构下,ISA_MASK的二进制表现如上所示,其中为1的一共有44位,isa.bits & ISA_MASK操作得出的正是当前类的指针。
objc_class
接下来看objc_class的定义
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...
}
可以看出,Class是指向objc_class结构体的指针。objc_class继承自objc_object,ISA就是父结构体objc_object中的变量isa。
Class ISA
实例的ISA指向类对象,类对象的ISA指向元类,Class是指针类型,占8位。Class superclass
父类,Class类型,占8位。cache用于缓存指针和 vtable,加速方法的调用,后续会有进一步的分析。占16位。
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct cache_t {
...
explicit_atomic<struct bucket_t *> _buckets; //指针 8位
explicit_atomic<mask_t> _mask; //uint32_t 4位
...
...
#if __LP64__
uint16_t _flags; //uint16_t 2位
#endif
uint16_t _occupied; //uint16_t 2位
public:
...
};
class_data_bits_t存储类的方法、属性、遵循的协议等信息的地方。
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
...
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
};
friend关键字解释:详解C++ friend关键字
class_data_bits_t结构体内部只有一个64位的bits用于存储与类有关的信息。我们看到注释,class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标志。
class_rw_t
执行 class_data_bits_t 结构体中的 data() 方法或者调用 objc_class 中的 data() 方法会返回同一个 class_rw_t * 指针,因为 objc_class 中的方法只是对 class_data_bits_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;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
...
}
在class_rw_t结构体中还存在一个ro_or_rw_ext的变量,该变量支持两种类型模板,分别是 class_ro_t和class_rw_ext_t,分别看一下这两个结构体的组成如下:
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;
...
}
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_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,而class_rw_ext_t除了包含一个class_ro_t *指针外也包含了一些methods、properties、protocols等信息。关于这两个结构体的信息可以观看WWDC2020的视频,上面有做介绍。
在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};
}
}
通过这些方法,我们可以获取类对应的方法、属性以及协议列表。
lldb验证
接下来我们对类的结构进行验证。修改类ZHYPerson的代码如下:
@protocol ZHYPersonProtocol1 <NSObject>
@property (nonatomic, copy) NSString *protocolProperty1;
- (void)protocolMethod1;
@end
@protocol ZHYPersonProtocol2 <NSObject>
@property (nonatomic, copy) NSString *protocolProperty2;
- (void)protocolMethod2;
@end
@interface ZHYPerson : NSObject<ZHYPersonProtocol1, ZHYPersonProtocol2>{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
增加一个ZHYPerson的分类
@interface ZHYPerson (Category)
- (void)categoryMethod;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
Class pClass = object_getClass(person);
}
return 0;
}
首先通过lldb打印pClass的内存分布。
(lldb) x/5gx pClass
0x100001620: 0x00000001000015f8 0x0000000100332148
0x100001630: 0x000000010212a1a0 0x0001802400000003
0x100001640: 0x000000010212a124
其中,0x100001620为的pClass在堆上的地址,该地址存放的内容为0x00000001000015f8,我们已经知道:
0x00000001000015f8为objc_class中的isa占8位0x0000000100332148为objc_class中的superclass占8位0x000000010212a1a0 0x0001802400000003为objc_class中的cache0x000000010212a124则为objc_class中的class_data_bits_t,指针为0x100001640。
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
获取class_rw_t。
(lldb) p (class_data_bits_t*)0x100001640
(class_data_bits_t *) $1 = 0x0000000100001640
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010212a120
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294972640
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
根据前面WWDC视频可以知道,在没有进行动态添加方法等操作的时候,ro_or_rw_ext是以class_ro_t的形式存在的,我们接下来验证一下:
p (class_ro_t*)4294972640
(class_ro_t *) $4 = 0x00000001000014e0
(lldb) p *$4
(class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000e30 "\x02"
name = 0x0000000100000e26 "ZHYPerson"
baseMethodList = 0x0000000100001070
baseProtocols = 0x00000001000014c8
ivars = 0x0000000100001528
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100001570
_swiftMetadataInitializer_NEVER_USE = {}
}
接下来我们根据class_rw_t提供的方法来校验一下上面的信息是否正确。
(lldb) p $3.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100001070
arrayAndFlag = 4294971504
}
}
}
(lldb) p $3.properties()
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100001570
arrayAndFlag = 4294972784
}
}
}
(lldb) p $3.protocols()
(const protocol_array_t) $8 = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x00000001000014c8
arrayAndFlag = 4294972616
}
}
}
通过lldb可以发现,通过class_rw_t方法获取的方法、属性、协议列表和class_ro_t结构体中的列表是一一对应的。
methods
(lldb) p $3.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100001070
arrayAndFlag = 4294971504
}
}
}
list和baseMethodList的地址是一样的。
(lldb) p $6.list
(method_list_t *const) $9 = 0x0000000100001070
(lldb) p *$9
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 5
first = {
name = "categoryMethod"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000100000b10 (objc-debug`-[ZHYPerson(Category) categoryMethod])
}
}
}
(lldb) p $10.get(0)
(method_t) $11 = {
name = "categoryMethod"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000100000b10 (objc-debug`-[ZHYPerson(Category) categoryMethod])
}
(lldb) p $10.get(1)
(method_t) $12 = {
name = "sayHello"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000100000b30 (objc-debug`-[ZHYPerson sayHello])
}
(lldb) p $10.get(2)
(method_t) $13 = {
name = ".cxx_destruct"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000100000ba0 (objc-debug`-[ZHYPerson .cxx_destruct])
}
(lldb) p $10.get(3)
(method_t) $14 = {
name = "setNickName:"
types = 0x0000000100000efc "v24@0:8@16"
imp = 0x0000000100000b70 (objc-debug`-[ZHYPerson setNickName:])
}
(lldb) p $10.get(4)
(method_t) $15 = {
name = "nickName"
types = 0x0000000100000e4d "@16@0:8"
imp = 0x0000000100000b40 (objc-debug`-[ZHYPerson nickName])
}
在methods的list中存在5个方法,分别是:
- categoryMethod
- sayHello
- .cxx_destruct
- setNickName
- nickName
其中除了.cxx_destruct方法外的4个方法都是我们代码生成的方法,虽然我们实现了ZHYPersonProtocol1协议,但是我们并没有实现protocolMethod1方法,因此方法列表中并没有protocolMethod1
ivars
(lldb) p $5.ivars
(const ivar_list_t *) $16 = 0x0000000100001528
(lldb) p *$16
(const ivar_list_t) $17 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000015e8
name = 0x0000000100000dc4 "hobby"
type = 0x0000000100000f07 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $17.get(0)
(ivar_t) $18 = {
offset = 0x00000001000015e8
name = 0x0000000100000dc4 "hobby"
type = 0x0000000100000f07 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $17.get(1)
(ivar_t) $19 = {
offset = 0x00000001000015f0
name = 0x0000000100000dca "_nickName"
type = 0x0000000100000f07 "@\"NSString\""
alignment_raw = 3
size = 8
}
在ivars的列表中一共有两个成员变量,分别是:
- hobby
- _nickName
虽然我们实现了ZHYPersonProtocol1协议,但是协议只会提供对应方法的声明,因此并不存在名为_protocolProperty1的实例变量。
properties
接下来验证properties。
(lldb) p $7.list
(property_list_t *const) $20 = 0x0000000100001570
(lldb) p *$20
(property_list_t) $21 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 6
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}
(lldb) p $21.get(0)
(property_t) $22 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $21.get(1)
(property_t) $23 = (name = "protocolProperty1", attributes = "T@\"NSString\",C,N")
(lldb) p $21.get(2)
(property_t) $24 = (name = "hash", attributes = "TQ,R")
(lldb) p $21.get(3)
(property_t) $25 = (name = "superclass", attributes = "T#,R")
(lldb) p $21.get(4)
(property_t) $26 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $21.get(5)
(property_t) $27 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")
其中,nickName、protocolProperty1是我们代码加上的属性,hash、superclass、description和debugDescription为协议NSObject的属性,因为hobby只是成员变量,因此不存在与属性列表中。
protocols
(lldb) p $8.list
(protocol_list_t *const) $28 = 0x00000001000014c8
(lldb) p *$28
(protocol_list_t) $29 = (count = 1, list = protocol_ref_t [] @ 0x00007f8eb827a368)
(lldb) p (protocol_t*)$29.list[0]
(protocol_t *) $31 = 0x00000001000016a8
(lldb) p *$31
(protocol_t) $32 = {
objc_object = {
isa = {
cls = Protocol
bits = 4298318032
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537289754
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100000e13 "ZHYPersonProtocol1"
protocols = 0x00000001000013c8
instanceMethods = 0x00000001000013e0
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x0000000000000000
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x0000000100001430
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100001448
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
(lldb) p $32.instanceMethods
(method_list_t *) $33 = 0x00000001000013e0
(lldb) p *$33
(method_list_t) $34 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 3
first = {
name = "protocolMethod1"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000000000000
}
}
}
(lldb) p $34.get(0)
(method_t) $35 = {
name = "protocolMethod1"
types = 0x0000000100000e32 "v16@0:8"
imp = 0x0000000000000000
}
(lldb) p $34.get(1)
(method_t) $36 = {
name = "protocolProperty1"
types = 0x0000000100000e4d "@16@0:8"
imp = 0x0000000000000000
}
(lldb) p $34.get(2)
(method_t) $37 = {
name = "setProtocolProperty1:"
types = 0x0000000100000efc "v24@0:8@16"
imp = 0x0000000000000000
}
(lldb)
(lldb) p $32.instanceProperties
(property_list_t *) $38 = 0x0000000100001430
(lldb) p *$38
(property_list_t) $39 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "protocolProperty1", attributes = "T@\"NSString\",C,N")
}
}
(lldb) p $32.protocols
(protocol_list_t *) $40 = 0x00000001000013c8
(lldb) p *$40
(protocol_list_t) $41 = (count = 1, list = protocol_ref_t [] @ 0x00007f8ed2c2b438)
(lldb) p (protocol_t*)$41.list[0]
(protocol_t *) $42 = 0x0000000100001648
(lldb) p *$42
(protocol_t) $43 = {
objc_object = {
isa = {
cls = nil
bits = 0
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100000e0a "NSObject"
protocols = 0x0000000000000000
instanceMethods = 0x00000001000010f0
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x00000001000012c0
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x00000001000012e0
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100001328
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
$32是一个类型为protocol_t的结构体,观察内部结构可以确定这个结构体就是我们通过代码声明的ZHYPersonProtocol1协议,同时我们可以发现协议继承了objc_object,也是个对象。协议的内部包含了包含了协议名ZHYPersonProtocol1,同时还有protocolMethod1、protocolProperty1、setProtocolProperty1:三个方法。
同时也可以发现ZHYPersonProtocol1内部还有一个协议列表,里面包含了名为NSObject的协议。经过上述lldb的打印,我们也验证了一个事实:
协议只提供了属性对应的set/get方法,并没有提供对应的实现以及相应的实例变量
以上是对class_rw_t的内部结构验证。
动态
之前我们验证的都是在编译期就确定了的内容,因为oc是一门动态的语言,我们可以通过运行时动态的给类添加属性、方法等等。这些内容在此就不验证了,验证的方法和上面是一样的。
总结
类本质也是一个继承自objc_object的objc_class结构体。它的isa指向其元类。内部主要有一下几个变量:
- ISA ---isa指针
- superclass ---父类
- cache ---缓存
- bits ---存放类的一些信息
64位系统下占8个字节。
NSObject协议
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;
@end