一、前言
前边文章我们介绍了OC对象的alloc
流程和isa
的分析,接下来我们来探索一下OC中的类。
二、内存中的类
我们都知道同一个类的对象在内存中是可以创建多个的,那么类在内存中能不能创建多个呢。写了以下代码验证一下:
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
由此可见,每个类在内存中有且只有一个,也就是类对象只会在工程编译时创建一次。
三、类的生成
接下来我们探索一下类是怎样生成的。
- 我们创建如下类:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *nickName;
@end
@implementation LGPerson
@end
- 编译一下,然后使用
clang
生成cpp
文件。
关于clang生成cpp的命令
// 常用方式
clang -rewrite-objc main.m -o main.cpp// 存在UIKit等其他动态引用库时
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot/Application/Xcode.app/Comtents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m// 模拟器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp// 真机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
- 打开
cpp
文件,寻找LGPerson
,可以看到下方内容:
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nickName;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_nickName;
};
// @property (nonatomic, copy) NSString *nickName;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
// @end
NSObject_IVARS
的结构。生成NSObject
类的时候,如下:
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
因此我们可以得出,类的本质是继承于objc_object
的struct
结构体,而类的属性会在编译时生成ivars
、setter
和getter
,还生成了一个带下划线的_nickName
。而NSObject_IVARS
则是继承与NSObject_IMPL
的一个结构体,结构体内存在一个Class
类型的isa
,也就是说每个类在创建的时候,都会从NSObject
继承一个isa
。
四、类对象的结构
在源码objc-750
里我们可以通过寻找到类Class
的父类objc_class
,objc_class
是继承于objc_object
的结构体(万物皆对象)。
-
objc_object
、objc_class
的结构如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
// Class ISA; //NSObject 继承的isa, 8字节
Class superclass; // 8字节
cache_t cache; //结构体,32字节 // 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);
}
/*下方省略部分代码*/
-
cache_t
的结构,注意结构体指针和结构体的区别。结构体的大小和结构体内的内容大小有关,结构体指针就是8字节,struct
声明的带*
的属于结构体指针。
struct cache_t {
struct bucket_t *_buckets; //结构体指针, 8字节
mask_t _mask; // uint32_t类型,4字节
mask_t _occupied; // uint32_t类型,4字节
/**下方都是函数实现,不占用内存,不显示了*/
-
class_rw_t
的结构
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; //方法列表
property_array_t properties; //属性列表
protocol_array_t protocols; //协议列表
/**省略下方代码*/
-
class_ro_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;//ivar列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; //属性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
五、类对象结构的验证
接下来我们就验证一下类的属性和方法的存储的位置。
- 首先,我们创建类
LGPerson
的.h文件如下,包括一些属性和方法。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
//方法在.m中都有实现
- (void)sayHello;
+ (void)sayHappy;
@end
NS_ASSUME_NONNULL_END
在main
函数里打断点,控制台使用lldb
打印LGPerson
类信息如下:
(lldb) x/4gx LGPerson.class // 输出前四个内存值
0x1000023a8: 0x001d800100002381 0x0000000100b37140
0x1000023b8: 0x00000001003da280 0x0000000000000000
//根据objc_class的结构,bits在第四个位置
//前边三个isa、superclass、cache共占用32字节
//那么需要首地址平移32字节到bits,32转为16进制就是0x20
(lldb) p/x 0x1000023a8 + 0x20
(long) $1 = 0x00000001000023c8 //获取到bits
(lldb) p (class_data_bits_t *)$1 //强转
(class_data_bits_t *) $2 = 0x00000001000023c8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010194f5b0 //获取rw
(lldb) p *$3 //输出rw
(class_rw_t) $4 = {
flags = 2148139008
version = 0
ro = 0x00000001000022f8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002230
arrayAndFlag = 4294976048
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022e0
arrayAndFlag = 4294976224
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
(lldb) p $4.properties //输出properties
(property_array_t) $5 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022e0
arrayAndFlag = 4294976224
}
}
}
(lldb) p $5.list
(property_list_t *) $6 = 0x00000001000022e0
(lldb) p *$6 //输出properties的list
//在这里只获取到了属性name,count =1 ,那么hobby在哪啊
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}//未找到hobby
(lldb) p $4.methods //获取方法列表
(method_array_t) $8 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002230
arrayAndFlag = 4294976048
}
}
}
(lldb) p $8.list
(method_list_t *) $9 = 0x0000000100002230
(lldb) p *$9
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
//第一个方法是sayHello
first = {
name = "sayHello"
types = 0x0000000100001f8d "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
(lldb) p $10->get(1)//第2个方法是cxx_destruct
(method_t) $11 = {
name = ".cxx_destruct"
types = 0x0000000100001f8d "v16@0:8"
imp = 0x0000000100001c90 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
Fix-it applied, fixed expression was:
$10.get(1)
(lldb) p $10->get(2) //第3个方法是name的getter
(method_t) $12 = {
name = "name"
types = 0x0000000100001f95 "@16@0:8"
imp = 0x0000000100001c20 (LGTest`-[LGPerson name] at LGPerson.h:16)
}
Fix-it applied, fixed expression was:
$10.get(2)
(lldb) p $10->get(3) //第4个方法是name的setter
(method_t) $13 = {
name = "setName:"
types = 0x0000000100001f9d "v24@0:8@16"
imp = 0x0000000100001c50 (LGTest`-[LGPerson setName:] at LGPerson.h:16)
}
Fix-it applied, fixed expression was:
$10.get(3)
(lldb) //没找到sayHappy方法
从上边的lldb
打印结果(上边每一步都有注释)看来,在class_rw_t
的properties
里并未找到hobby
,在class_rw_t
的methods
里并未找到方法sayHappy
。我们知道class_rw_t
的class_ro_t
也有属性和方法列表,那么我们在class_ro_t
里找一下吧。结果如下:
(lldb) x/4gx LGPerson.class
0x1000023a8: 0x001d800100002381 0x0000000100b37140
0x1000023b8: 0x00000001003da280 0x0000000000000000
(lldb) p/x 0x1000023a8 + 0x20
(long) $1 = 0x00000001000023c8
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000023c8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100f42b80
(lldb) p $3->ro //获取到ro
(const class_ro_t *) $4 = 0x00000001000022f8
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f8b "\x02"
name = 0x0000000100001f82 "LGPerson"
baseMethodList = 0x0000000100002230
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002298
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022e0
}
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x00000001000022e0
(lldb) p *$6
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
//属性列表里获取到name
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x0000000100002298
(lldb) p *$8
(const ivar_list_t) $9 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100002370
//ivars里获取到hobby
name = 0x0000000100001e72 "hobby"
type = 0x0000000100001fa8 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $9.get(1) //ivars里获取到_name
(ivar_t) $10 = {
offset = 0x0000000100002378
name = 0x0000000100001e78 "_name"
type = 0x0000000100001fa8 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $5.baseMethodList
(method_list_t *const) $11 = 0x0000000100002230
(lldb) p *$11
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
//sayHello方法
name = "sayHello"
types = 0x0000000100001f8d "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
(lldb) p $12.get(1)
(method_t) $13 = {
name = ".cxx_destruct"
types = 0x0000000100001f8d "v16@0:8"
imp = 0x0000000100001c90 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $12.get(2) //name的getter方法
(method_t) $14 = {
name = "name"
types = 0x0000000100001f95 "@16@0:8"
imp = 0x0000000100001c20 (LGTest`-[LGPerson name] at LGPerson.h:16)
}
(lldb) p $12.get(3) //name的setter方法
(method_t) $15 = {
name = "setName:"
types = 0x0000000100001f9d "v24@0:8@16"
imp = 0x0000000100001c50 (LGTest`-[LGPerson setName:] at LGPerson.h:16)
}
(lldb)
由上边结果可以看出,在class_rw_t
的class_ro_t
里找到了hobby
、_name
、name
和sayHello
、name:
、setName:
,就差sayHappy
方法,其实我们也发现sayHello
和sayHappy
是不同的,sayHappy
是类方法,那么我们对比一下对象方法和类方法的调用,这个方法是不是应该存在LGPerson
的元类里,接下来我们验证一下。
- 获取
LGPerson
的元类。
Class metaPerson = objc_getMetaClass("LGPerson");
NSLog(@"~~~~~%@",metaPerson);
-
lldb
打印
(lldb) x/4gx metaPerson //获取元类的前四个内存地址
0x100002370: 0x001d800100b370f1 0x0000000100b370f0
0x100002380: 0x00000001003da280 0x0000000000000000
(lldb) p/x 0x100002370 + 0x20 //内存平移到bits
(long) $1 = 0x0000000100002390
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100002390
(lldb) p $2->data() //获取data,即rw
(class_rw_t *) $3 = 0x00000001021bafc0
(lldb) p $3->ro //获取ro
(const class_ro_t *) $4 = 0x00000001000021e8
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f85 "LGPerson"
baseMethodList = 0x00000001000021c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
(lldb) p $5.baseMethodList
(method_list_t *const) $6 = 0x00000001000021c8
(lldb) p *$6 //找到了sayHappy方法
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f90 "v16@0:8"
imp = 0x0000000100001bf0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
}
}
(lldb)
在LGPerson
的元类里找到了sayHappy
方法,也就验证了我们的猜想。
六、总结
1、每个类在内存中有且只有一个,占用一份内存。
2、类是在编译期生成的。类的本质是结构体objc_class
,objc_class
继承于objc_object
,验证了万物皆对象。
3、类的属性在编译期会生成ivars
、setter
和getter
,还生成了一个带下划线的实例变量。
4、类的结构里边包括isa
、superclass
、cache
、bits
。
4、类对象的对象方法、属性和成员变量会存在与类的class_ro_t
里。类对象的类方法则会存在于类的元类class_ro_t
里。