类的结构分析
类分析初探
首先我们先创建一个类LGPerson
,再创建一个继承于LGPerson
的子类LcrTeacher
。
@interface LGPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
-(void)sayHello;
+(void)say88;
@end
#import "LGPerson.h"
@implementation LGPerson
-(void)sayHello{
}
+(void)say88{
}
@end
#import "LGPerson.h"
@interface LcrTeacher : LGPerson
@end
#import "LcrTeacher.h"
@implementation LcrTeacher
@end
在main.m文件中我们创建两个对象:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//ISA_MASK 0x00007ffffffffff8ULL
LGPerson *person = [LGPerson alloc];
LcrTeacher *teacher = [LcrTeacher alloc];
NSLog(@"%@ ---- %@",person,teacher);
}
return 0;
}
利用lldb
调试我们打印出下面图片中一些信息:
如上图所示,1
、2
处的打印结果都为LGPerson
,1
处是因为person
对象的指针指向类,所以当此对象的指针地址&ISA_MASK
时,所得到的就是LGPerson
类地址,打印即为LGPerson
。2 处打印的地址是类的isa&ISA_MASK
后得到的元类
的信息,那么这个元类
到底是什么?
元类
对象的isa
指向的是所对应的类的信息,类其实也是一个对象
,它的内存信息中保存的位域信息
所指向的就是由苹果定义的元类
:
1.元类是由系统定义
并创建
的,这个过程是在编译期间
完成的。
2.元类的类对象
是类
,元类中保存着类的相关类方法信息
。
3.元类是没有自己的名字,由于与当前类相关联
,所以苹果直接拿当前类名命名元类。
由此,上面的1
、2
处打印的都为LGPerson
就是元类
导致的。
通过lldb可以调试元类信息链,即isa走向,对象--->类--->元类--->根元类--->根类(NSObject):
上图可见:
-
对象
的isa指向类
。 -
类
的isa指向元类
。 -
元类
的isa指向根元类
。 -
根元类
的isa指向自己
。
著名isa及Superclass走位图
结合上面的工程,将我们自定义的类信息放上去,isa
及Superclass
走向一目了然:
-
isa
走向:-
teacher
走位链:teacher
(对象
) --->LcrTeacher
(类
) --->LcrTeacher
(元类
) --->NSObject
(根元类
)--->NSObject
(自己
) 。 -
person
走位链:person
(对象
)--->LGPerson
(类
) --->LGPerson
(元类
) --->NSObject
(根元类
)--->NSObject
(自己
) 。
-
-
Superclass
走向:-
类
的走向链:Lcrteacher
(子类)--->LGPerson
(父类) --->NSObject
(根类) --->nil
。 -
元类
的走向链:LcrTeacher
(子类元类) --->LGPerson
(父类元类) --->NSObject
(根元类) --->NSObject
(根类) --->nil
。
-
objc_object & objc_class
在继续下面类的结构分析之前,我们先来了解objc_object 和 objc_class两个概念,以及它们之间是怎样的关系?
objc_class:
在上篇文章iOS底层 - isa与类关联的原理 中,通过clang编译过的main.cpp
文件中可以看到类的底层结构:
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
NSObject
被编译成了一个NSObject_IMPL
结构体,里面有一个Class
类型的isa
,类型Class
是由objc_class
定义的。
在源码objc4-781
源码中我们搜索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
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);
}
//略
}
从objc_class
的源码中看出objc_class
是继承自objc_object
的。
objc_object:
查看源码,在objc.h及objc_private.h两个文件中分别能找到objc_object的源码实现:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
//略
}
那么objc_class
和objc_object
以及 NSObject
、对象
到底是什么关系?
-
objc_class
继承自objc_object
,所以objc_class
中有一个继承过来的isa
属性。 - 通过
clang
编译的main.cpp 中看出NSObject_IMPL
中有一个Class
类型的isa
,而Class
是由objc_class
定义的,而objc_class
又继承自objc_object
,所以可知所有以NSObject
所创建的对象都有一个isa
属性。 - 即
objc_object
是一个对象创造的根类
,所有对象创建之后都带有objc_object
的特性,带有一个isa
属性。
总结
- 所有的
对象
+类
+元类
都有isa
。 - 所有的对象都是由
objc_object
继承来的。 - 简单概括就是
万物皆对象
,万物皆来源于objc_object
,有以下两点结论:- 所有以
objc_object
为模板 创建的对象,都有isa属性
- 所有以
objc_class
为模板,创建的类,都有isa属性
- 所有以
- 在结构层面可以通俗的理解为
上层OC
与底层
的对接:-
下层
是通过 结构体 定义的 模板,例如objc_class
、objc_object
-
上层
是通过底层的模板创建的 一些类型,例如LGPerson
可以用下图表示出:
-
类的结构分析
上面分析可知类是以objc_class为模版创建的,那么我们就从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
class_rw_t *data() const {
return bits.data();
}
//省略下方方法
}
- 继承于objc_object,默认带有一个isa,大小为8字节。
- 父类superclass,objc_class定义的一个Class类型的结构体指针,大小也为8字节。
- cache 类的缓存信息,结构体cache_t类型,我们在下面会计算cache属性大小。(cache是一个结构体,而不是结构体指针,不能简单地认为大小8字节)
- bits 存放类的一些信息,class_data_bits_t 结构体类型,接下来我们就针对这个bits里存放的信息进行一步步探究。
cache大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; //8
explicit_atomic<mask_t> _mask; //4
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; // 8
mask_t _mask_unused; // 4
//过滤一些静态的,因为静态不计入结构体的大小
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
//省略下面的方法,结构体大小只和属性有关
}
有上面代码可得,cache大小为16,所以8 + 8 + 16 ,想获得bts里的信息的话,我们可利用指针偏移32来获取类信息中的bits,这样就可以访问类相关的一些信息了。
其中data()方法获取信息,是objc_class提供的方法:
class_rw_t *data() const {
return bits.data();
}
目前已经拿到了bits信息,但是还不够,我们暂时还看不到一些具体的类的信息,所以我们继续往下探索:
bits 查看属性列表 property_list
由于上方获取到的bits是class_rw_t类型结构体, 查看class_rw_t 源码,我们可以找到类的一些相关信息:
可以看到,方法列表methods
、属性列表properties
、协议列表protocols
等类的信息。
既然有这些信息,我们不妨试着去获取它们,并且将它们打印:
所以,通过点方法
.properties()
,就可以获取到类的属性列表,再通过p *$6
打印$6
里的数据,可以获得property_list_t
类型的属性列表,进而打印.get()
方法,获取每一个属性。.get()
一直获取属性的话,会造成数组越界的结果,因为类的属性个数是固定的,文章头部的LGPerson
有两个属性(nickName
和name
),所以此处只能.get()
到这两个属性.
bits 查看属性列表 method_list
@interface LGPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
-(void)sayHello;
+(void)say88;
@end
下面再利用lldb调试,是否也能像属性列表一样方式去查看类的方法列表:
由上图可知,可以利用.methods()
获取bits
信息中的方法列表
,获取到的也是一个method_list_t
类型的方法列表信息,然后利用.get()
方法一个个获取到方法。LGPerson
共有6个方法,实例方法sayHello
,.cxx_destruct
,name
属性的set
和get
,nickName
属性的set
和get
。
最后留下两个问题:成员变量hobby
在哪里?以及类方法+say88
又在哪里?下篇文章我们一一揭晓。