IOS类的相关分析
本文章将对IOS类的相关知识点进行分析,主要是基于objc源码在macOS系统中对ios的类结构进行底层分析。分为四个部分:1.类的结构分析 2.方法的存储(get/set方法生成规则) 3.类方法的存储 4.cache的结构分析,源码方面还是使用obj4-818.在我之前的文章中可以找到。
类的结构分析
首先先上一张苹果官方的一张类结构对,针对这张图我们进行类结构分析,去验证类的结构
我们创建一个Teacher —> Person -> NSOjbect这个的一个继承结构。并且添加了属性和实现一些方法
对象在alloc申请内存的时候,会走到initIsa函数中去初始化ISA,在初始化isa的时候会调用set方法将它的类对象赋值进去。
通过代码跟踪在调用calloc的时候Class传入的就是Teacher的类对象
Class是一个结构体的objc_class,而objc_class是继承了objc_object。去掉不需要关注的内容,具体的结构体内容如下:
struct objc_class : objc_object {
isa_t isa //来自于objc_object的一个属性
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
struct objc_object {
private:
isa_t isa;
}
第一个阶段证实Teacher对象里面的ISA指向的是Teacher类,Teacher对象是继承了Person
**(lldb) p/x LGTeacher.class
**(Class) $6 = 0x0000000100008450 LGTeacher //类对象的地址
**(lldb) p/x 0x011d800100008455 & 0x00007ffffffffff8ULL //对象的ISA与过之后获取到的值
**(unsigned long long) $7 = 0x0000000100008450
**(lldb) po 0x0000000100008450
**LGTeacher
第二阶段验证Teacher类的ISA指向元类
(lldb) x/4gx LGTeacher.class
0x100008450: 0x0000000100008478 0x00000001000084c8
0x100008460: 0x00000001003623b0 0x0000803c00000000
(lldb) p/x 0x0000000100008478 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x0000000100008478
(lldb) po 0x0000000100008478 -- 元类
LGTeacher
第三阶段依据objc_class的机构,isa占8个字节,下一个就是superclass,验证Teacher类的父类是Person
(lldb) x/4gx 0x0000000100008450
0x100008450: 0x0000000100008478 0x00000001000084c8
0x100008460: 0x00000001003623b0 0x0000803c00000000
(lldb) po 0x00000001000084c8
LGPerson
第四阶段,Teacher这一层验证完毕里,再验证Teacher对应的元类里面是父级的元类
(lldb) x/4gx 0x0000000100008478 --Teacher对应的元类
0x100008478: 0x000000010036a0f0 0x00000001000084a0
0x100008488: 0x0000000101304aa0 0x0005e03500000007
(lldb) p/x 0x000000010036a0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $18 = 0x000000010036a0f0 -- Teacher元类的ISA是根元类
(lldb) po 0x000000010036a0f0
NSObject
(lldb) p/x NSObject.class
(Class) $21 = 0x000000010036a140 NSObject类
(lldb) x/4gx 0x000000010036a140
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x0000000100778eb0 0x0002801000000003
(lldb) po 0x000000010036a0f0 --- NSObject类对应的元类 也就是根元类
NSObject
第五阶段,验证person类的元类,person的元类指向根元类,父类指向NSObject
(lldb) p/x 0x00000001000084c8 -- LGPerson类
(long) $23 = 0x00000001000084c8
(lldb) x/4gx 0x00000001000084c8
0x1000084c8: 0x00000001000084a0 0x000000010036a140
0x1000084d8: 0x00000001003623b0 0x0000803400000000
(lldb) p/x 0x00000001000084a0 & 0x00007ffffffffff8ULL
(unsigned long long) $24 = 0x00000001000084a0 --LGPerson类的元类
(lldb) po 0x000000010036a140 -- LGPerson类的父类
NSObject
(lldb) x/4gx 0x00000001000084a0
0x1000084a0: 0x000000010036a0f0 0x000000010036a0f0
0x1000084b0: 0x000000010160eef0 0x0002e03500000003
(lldb) po 0x000000010036a0f0 -- LGPerson类的根元类
NSObject
第六阶段验证NSObject类的父类是nil
(lldb) x/4gx 0x000000010036a140 -- NSObject类
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x0000000100778eb0 0x0002801000000003
方法和属性的存储
在类的结构中有cache和bits,分别都存的时候东西?对象开辟空间所需要的属性方法都存在哪里? 带着疑问从bits开始入手.class_data_bits_t结构体里面并没有什么属性,但有个关键方法
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
class_rw_t结构中发现了class_ro_t class_rw_ext_t
class_ro_t结构中发现了property_list_t ivar_list_t
class_rw_ext_t结构体中发现了method_array_t
通过LLDB调试找到了类的成员变量和属性
(lldb) x/6gx LGPerson.class
0x1000084c8: 0x00000001000084a0 0x000000010036a140
0x1000084d8: 0x00000001003623b0 0x0000803400000000
0x1000084e8: 0x000000010160e4f4 0x00000001000b9980
(lldb) p/x 0x1000084e8
(long) $37 = 0x00000001000084e8
(lldb) p (class_data_bits_t *)0x00000001000084e8
(class_data_bits_t *) $38 = 0x00000001000084e8
(lldb) p $38->data()
(class_rw_t *) $39 = 0x000000010160e4f0
(lldb) p *$39
(class_rw_t) $40 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000576
}
}
firstSubclass = LGTeacher
nextSiblingClass = NSUUID
}
(lldb) p $39.ro()
(const class_ro_t *) $43 = 0x0000000100008200
Fix-it applied, fixed expression was:
$39->ro()
(lldb) p *$43
(const class_ro_t) $44 = {
flags = 388
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000100003e22 "\U00000011\U00000011"
nonMetaclass = 0x0000000100003e22
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003e19 "LGPerson"
}
}
baseMethodList = 0x0000000100008248
baseProtocols = nil
ivars = 0x0000000100008340
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000083c8
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $44.ivars
(const ivar_list_t *const) $45 = 0x0000000100008340
(lldb) p *$45
(const ivar_list_t) $46 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $46.get(0) //拿到成员变量
(ivar_t) $47 = {
offset = 0x0000000100008430
name = 0x0000000100003eca "_age"
type = 0x0000000100003f5d "i"
alignment_raw = 2
size = 4
}
(lldb) p $43.baseProperties //class_ro_t结构体中的baseProperties记录了属性的描述
(property_list_t *const) $51 = 0x00000001000083c8
Fix-it applied, fixed expression was:
$43->baseProperties
(lldb) p *$51
(property_list_t) $52 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 4)
}
(lldb) p $52.get(0)
(property_t) $53 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $39.properties() //class_rw_t里面的properties记录了属性的描述
(const property_array_t) $57 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000083c8
}
arrayAndFlag = 4295001032
}
}
}
Fix-it applied, fixed expression was:
$39->properties()
(lldb) p $57.list
(const RawPtr<property_list_t>) $58 = {
ptr = 0x00000001000083c8
}
(lldb) p $58.ptr
(property_list_t *const) $59 = 0x00000001000083c8
(lldb) p *$59
(property_list_t) $60 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 4)
}
(lldb) p $60.get(0)
(property_t) $61 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
当前属性已经找到了在类的位置,下面找方法class_rw_t里面的methods是空的。是因为method_t结构体成员变量在big结构体里面。
(lldb) p $39.methods
(const method_array_t) $62 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008248
}
arrayAndFlag = 4295000648
}
}
}
Fix-it applied, fixed expression was:
$39->methods()
(lldb) p $62.list
(const method_list_t_authed_ptr<method_list_t>) $63 = {
ptr = 0x0000000100008248
}
(lldb) p $63.ptr
(method_list_t *const) $64 = 0x0000000100008248
(lldb) p *$64
(method_list_t) $65 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 10)
}
(lldb) p $65.get(0)
(method_t) $66 = {}
(lldb) p $65.get(1)
(method_t) $67 = {}
(lldb) p $65.get(2)
(method_t) $68 = {}
(lldb) p $65.get(0).big //在big里面
(method_t::big) $69 = {
name = "saySomething"
types = 0x0000000100003f4a "v16@0:8"
imp = 0x0000000100003b50 (KCObjcBuild`-[LGPerson saySomething])
}
Fix-it applied, fixed expression was:
$65.get(0).big()
这里做一个总结:成员变量(实力变量)和属性的区别:属性clang优化后,就变成了_的实例变量,还有getset方法 下面用clang生成cpp看看llvm对属性方法做了怎样的处理。 发现用copy修饰的set方法是滴啊用了objc_setProperty方法。
copyWithZone
最终调用的reallySetProperty方法可以看到。copy修饰的属性,会调用copyWithZone生成新的newValue。
而strong assign修饰的只做了内存平移
类方法的存储
在Person类里面并没有找到+ (void)sayStatic;类方法。不在类方法里就是在元类的method列表中。 验证方式和前面去验证对象的方法一致,在这里不在贴代码了,就贴一个结果出来。
(lldb) p $10.get(0).big
(method_t::big) $11 = {
name = "sayStatic"
types = 0x0000000100003f4a "v16@0:8"
imp = 0x0000000100003b00 (KCObjcBuild`+[LGPerson sayStatic])
}
Fix-it applied, fixed expression was:
$10.get(0).big()
cache的内存结构
贴上一张cache的内存结构图,核心的结构体就是buckets
//cache_t结构体
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;//8
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
//找到核心的方法.
struct bucket_t *buckets() const;
(lldb) p/x LGTeacher.class //类对象
(Class) $34 = 0x0000000100008478 LGTeacher
(lldb) x/6gx 0x0000000100008478
0x100008478: 0x00000001000084a0 0x00000001000084f0
0x100008488: 0x00000001016040e0 0x0003804400000007
0x100008498: 0x0000000100665da4 0x000000010036a0f0
(lldb) p/x (cache_t *)0x100008488
(cache_t *) $35 = 0x0000000100008488
(lldb) p/x $35.buckets //找到buckets
(bucket_t *) $36 = 0x00000001016040e0
Fix-it applied, fixed expression was:
$35->buckets()
(lldb) p/x $35.buckets()
(bucket_t *) $37 = 0x00000001016040e0
Fix-it applied, fixed expression was:
$35->buckets()
(lldb) p/x $35.buckets()[1]
(bucket_t) $38 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = 0x00007fff7c384f71 ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0x0000000000347b98
}
}
}
Fix-it applied, fixed expression was:
$35->buckets()[1]
(lldb) p $38.sel()
(SEL) $39 = "class"
(lldb) p/x $35.buckets()[0]
(bucket_t) $40 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = 0x0000000100003ee7 ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0x000000000000bf48
}
}
}
Fix-it applied, fixed expression was:
$35->buckets()[0]
(lldb) p $40.sel() //找到缓存的执行的方法
(SEL) $41 = "saySomething"
p $40.imp(nil,pClass)
通过对类的结构分析,可以更清晰了解到类的底层原理。如有有错误的地方,欢迎指出,交流。