本文旨在经过类&元类的建立时机
,类的结构及相关属性
,添加的类信息
等分析类在内存中的实际存在,并分享一些关于类的经典面试题
上文说到,对象经过isa
和类关联,同个类型的对象能够屡次建立,因此对象能够有多个。那么类呢,根据开发经验,很容易得出类在内存中只有一个,那究竟要怎么实锤呢。提供验证方式:html
machoView
查看新建一个项目,并建立一个CJPerson
类,在Products
目录下.app
结尾的文件尚未编译是报红的,c++
command + b
下变黑,Show In Finder
后显示包内容,把可执行文件拖入machoView
,
面试
machoView
中显示以下
缓存
能够看到,在DATA
段的_objc_classrefs
内已经加载了CJPerson
类,并指定了内存地址,说明类的建立是在编译时期,而且只有一份。bash
元类也是在编译期,由系统建立的,在个人理解,元类的做用是:
1.承接isa走向
2.缓存类方法
总以为苹果大费周章引入元类的概念,还有其余的做用,至因而什么,但愿能够指导我下
复制代码
在main.m
下初始化一个CJPerson
对象数据结构
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJPerson * person = [[CJPerson alloc]init];
NSLog(@"%@",person.class);
}
return 0;
}复制代码
用clang
编译下main.m
多线程
clang -rewrite-objc main.m -o main.cpp复制代码
打开编译后的main.cpp
文件,直接来到最后面,会看到初始化时候是调用objc_getClass
app
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
CJPerson * person = ((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CJPerson"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_3995175d2_v6g81lknk8jdwh0000gn_T_main_e6fa2f_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("class")));
}
return 0;
}复制代码
在文件内搜索objc_getClass
,看到ide
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);复制代码
再搜索objc_class
,找到重定义post
typedef struct objc_class *Class;复制代码
原来类是objc_class
重定义的,感受这个objc_class
很熟悉,好像在objc源码里面有看到过,那就搜索试试
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() {
return bits.data();
}
...
}复制代码
顺便贴出objc_object的结构
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};复制代码
ISA
是被注释的,不表明结构体内不包含它,由于objc_class
继承objc_object
,因此ISA
是来自于父类clang
编译下NSObject
能够获得objc_object
isa
的结构是isa_t
,但是这里使用class
接收是能够的,内部经过shift_class
获得class
总结:类的真正类型为objc_class,继承于objc_object(也就是NSObject的底层结构),说明万物皆对象,类也是一个对象,CJPerson严格来讲是类对象
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
}复制代码
根据objc_class
结构体能够清楚看到,类有4个属性,分别是isa
,superclass
,cache
,bits
isa
在(iOS底层-一应俱全的isa)中已经分析过了,占有8个字节,只是这里的isa是指向元类
superclass
就是指向的父类
typedef struct objc_class *Class;
复制代码
superclass
的结构一样是objc_class
,由于它是结构体指针
,占有8个字节
cache
是方法缓存,方法缓存涉及到方法查找流程
,缓存策略
,动态扩容
等,下一章详细说明,先看下cache_t
和bucket_t
结构
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}复制代码
struct bucket_t {
private:
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
...
}复制代码
buckets
是装方法的桶子,里面放着方式实现imp
,根据方法编号sel
生成的key
mask
是扩容因子,决定扩容的时机
occupied
是当前占用的容量
bucket_t
是结构体指针,占用8个字节,mask_t
就是int
,占用4个字节,因此mask
和occupied
各占4个字节
cache_t
是个结构体,结构体大小是内部全部大小的和,因此cache_t
占有16个字节
bits
是数据存放的地方,bits
里面有data
,data
是从macho
里面读取的class_rw_t
class_rw_t *data() {
return bits.data();
}复制代码
struct class_rw_t {
...
const class_ro_t *ro;
method_array_t methods;//方法列表
property_array_t properties;//属性列表
protocol_array_t protocols;//协议列表
...
}复制代码
class_rw_t
有方法列表,属性列表,协议列表等等
class_rw_t
里面有个class_ro_t
struct class_ro_t {
..
method_list_t * baseMethodList;//方法列表
protocol_list_t * baseProtocols;//协议列表
const ivar_list_t * ivars;//实例变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;//属性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};复制代码
class_ro_t
也有方法列表,属性列表,协议列表等,可是多了ivar_list_t实例变量列表。下面探索下二者等区别
类中添加实例变量sex
,属性age
,对象方法work
,类方法play
@interface CJPerson : NSObject{
NSString * sex;
}
@property (nonatomic , copy , readonly) NSString * age;
- (void)work;
+ (void)play;复制代码
object_getClass
输出下类对象
CJPerson * person = [CJPerson alloc];
Class cls = object_getClass(person);
NSLog(@"%@",cls);复制代码
x/4gx
打印cls
的内存地址以下:
经过类的数据结构分析,bits
位于ISA,superclass,cache
以后,这三者分别是8,8,16字节,根据内存偏移,
bits
在isa
地址后的32字节,
0x100001218 + 32字节 = 0x100001238复制代码
po
和p
直接打印都不行,那么须要用class_data_bits_t
类型强转打印
取出bits
内的class_rw_t
看到methods
中有3个方法,分别输出下
.cxx_destruct 系统添加c++的析构方法
work 对象方法
age 属性生成的age方法
method_t中的types,在苹果开发者文档中有详细说明
结论:有对象方法work
,竟然没有类方法play
如法炮制,输出下属性列表properties
结论:符合预期,只有一个添加的age
属性
那继续看下class_ro_t
结论:ivar_list_t
中有添加的实例变量sex
,还有一个_age
,这也符合常规认知,属性在底层会生成带下划线的实例变量
结论:和class_rw_t
中的methods
一致
结论:和class_rw_t
中的properties
一致
目前添加的实例变量,属性,对象方法都在内存中寻找到了,惟独缺乏类方法,不过根据经验能够知道,类方法是缓存在元类
中,那尝试去元类
搜索
经过isa_mask
找到元类,而后一步步找到baseMethodList
,果真在其中找到类方法play
class_ro_t
中的ivar_list_t
class_rw_t
中的property_array_t
和class_ro_t
中的的property_list_t
都存着一份,而且会生成实例变量,和对应的方法class_rw_t
中的method_array_t
和class_ro_t
中的的method_list_t
都存着一份类
里面元类
里面class_ro_t和class_rw_t内容大部分相同的缘由:
class_ro_t
存储了当前类在编译期就已经肯定的属性
、方法
以及遵循的协议,
class_rw_t
是在运行时才肯定,它会先将class_ro_t
的内容拷贝过去,而后再将当前类的分类的这些属性、方法等拷贝到其中。class_rw_t
是class_ro_t
的超集。
这是一道比较经典的面试题,CJPerson
继承NSObject
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[CJPerson class] isKindOfClass:[CJPerson class]]; //
BOOL re4 = [(id)[CJPerson class] isMemberOfClass:[CJPerson class]]; //
NSLog(@"%hhd%hhd%hhd%hhd",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[CJPerson alloc] isKindOfClass:[CJPerson class]]; //
BOOL re8 = [(id)[CJPerson alloc] isMemberOfClass:[CJPerson class]]; //
NSLog(@"%hhd%hhd%hhd%hhd",re5,re6,re7,re8);复制代码
输出为1000,1111
re1是NSObject调用类方法isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}复制代码
递归查找NSObject的元类是否存在,存在就继续找元类的父类,直到根元类指向NSObject的时候,返回YES复制代码
re2是NSObject调用类方法isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}复制代码
只对比一次,NSObject的元类是否等于NSObject,返回NO复制代码
re3是CJPerson调用类方法isKindOfClass
第一步就到CJPerson元类,而后一直递归下去,不会回到CJPerson类,返回NO复制代码
re4是CJPerson调用类方法isMemberOfClass
只对比一次CJPerson元类和CJPerson类,返回NO复制代码
re5是NSObject对象调用对象方法isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}复制代码
递归查找对象的父类是否等于NSObject,总有一次等于NSObject,返回YES复制代码
re6是NSObject对象调用对象方法isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}复制代码
只对比一次,NSObject对象的类是否等于NSObject,恰好继承关系知足复制代码
re7是CJPerson对象调用对象方法isKindOfClass
递归查找CJPerson对象的类是否等于CJPerson,第一次就知足继承关系复制代码
re8是CJPerson对象调用对象方法isMemberOfClass
只对比一次,CJPerson对象的类是否等于CJPerson,恰好继承关系知足复制代码
以上就是关于类的探索,下一章是cache_t
流程分析引出消息发送流程,后续继续更新类的底层结构,block,锁,多线程等底层探索,还有应用程序加载,启动优化,内存优化等相关知识点,敬请关注。