前言
上篇博客说完了对象的成员博客-isa结构分析,今天我们来研究一下对象的爸爸
,他就是类,相信大家都知道对象是由一个类通过调用(alloc init new
)等方法创建的。那么类里面都有哪些默认属性,今天我们就通过代码来看一下。
1.代码
我们打开之前博客用到的objc源码的项目,objc源码地址Source Browser,然后在main.m文件新建两个类Farther和Son,Son继承于Father,Father继承于NSObject,代码如下。
#import <Foundation/Foundation.h>
#import <objc/objc.h>
@interface Father : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHi;
+ (void)sayGoodBye;
@end
@implementation Father
- (void)sayHi {
NSLog(@"%s", __func__);
}
+ (void)sayGoodBye {
NSLog(@"%s", __func__);
}
@end
@interface Son : Father
@end
@implementation Son
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Son *s = [[Son alloc] init];
s.name = @"张山";
}
return 0;
}
然后我们用Clang编译器编译main.m
文件 成main.cpp
,具体方法在上篇博客里面,链接在上面。
然后我们打开main.cpp
文件,全局搜索struct objc_object
,然后发现7660行定义了*Class
是继承于结构体objc_class
。
接下来我们在代码里找不到
objc_class
定义了,这个时候我们就得去找苹果提供开源的objc4-781
源码了,在苹果网站下载Source Browser在源码里直接搜索
struct objc_class
,然后定位到 objc-runtime-new.h
文件
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
class_rw_t *data() const {
return bits.data();
}
// 省略部分代码.......
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
我们发现找到了结构体objc_class
的定义,它继承于objc_object
,万物皆对象,万物皆继承objc_object
,类之间的继承关系如下图,最后都走向了root class
也就是NSObject
->objc_class
->objc_object
我们接下来看下objc_class
里面定义哪些成员。
1.isa
指针(继承自objc_object
)
2.Class superclass;
指针
3.cache_t cache;
缓存指针和函数表
4.class_data_bits_t bits;
class_rw_t加上自定义的属性、方法,初始化的标记..
1.isa 指针
这个不多讲了,之前博客讲过isa
的用途和结构
2.Class superclass;
指针
superclass
也就是字面意思,它指向这个类的父类,比如上面LGSon
里面的superclass
就会指向它的父类LGFather
,LGFather
里面的superclass
就会指向所有类的根父类NSObject
。
3.cache
缓存
cache 结构为 cache_t,其定义如下:
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;
}
// 省略部分代码...
在这里我们计算一下cache所占用内存大小,其中if elseif
两个走向都是16字节。那么cache就是占16字节。接下来要用到。
4.bits
bits
的数据结构类型是 class_data_bits_t
,同时也是一个结构体类型。它存储着类的属性,实例方法等等,接下来我们就来验证一下。
我们都知道对象的指针指向对象的首地址也就是第一个属性isa
。而对象的存储是连续的,也就是从isa
地址往后打印就是对象的其他数据成员,而类对象也是对象,第一个isa
8字节,superclass
指针也是8字节,cache
上面计算了是16字节,加起来是32字节,那么bit
所在内存地址应该是对象首地址+32字节。
我们随意打个断点,然后控制台输入命令p/x LGFather.class
打印类对象LGFather
的16进制地址得到0x0000000100002320
。
然后我们拿首地址0x0000000100002320
+32,32是十进制转成16进制是0x20
,0x0000000100002320
+ 0x20
= 0x0000000100002340
,我们打印下这个地址验证一下。
我们看到
objc_class
里面的data()
返回的bits
里面的data
。然后我们继续p *$2
打印$2 = 0x0000000101104f50
地址里面存储什么数据,发现看不懂,但是我们看到$2
的数据类型是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 *>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>();
} else {
return extAlloc(v.get<const class_ro_t *>());
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
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;
}
return v.get<const class_ro_t *>();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
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};
}
}
};
我们把目光放在倒数最后三个函数声明,发现很眼熟,method_array_t methods() property_array_t properties() protocol_array_t protocols()
,这不就是方法数组,属性数组,协议数组嘛,接下来我们就对$2调用method_array_t methods() property_array_t properties()
两个函数试试。
我们看到输出了
property_array_t
结构的数据,里面有个list
,我们继续打印list
看看。发现他是一个指针,我们打印下指针指向的数据看看。
OK,我们看到了
LGFather
的属性name
,果然如我们猜测,类成员属性放在了bits里面,我们如火炮制的再看看打印methods()
。发现实例方法真的也保存在
bits
里面,我们看到方法数组里面count为4,第一个方法为sayHi
,那我们继续打印第2,3,4个方法看看能否找到类方法sayGoodbye
。我们发现除了属性
name
的get set
方法外没有其他方法了,我们的sayGoodbye
类方法存放到哪里呢?欲知后事如何,请听下回分解。
结论
万物皆对象:类的本质就是对象
类在 class_rw_t 结构中存储了编译时确定的属性、方法和协议等内容。
实例方法存放在类中