iOS底层原理之类,元类数据结构探索(下)
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层原理之类,元类数据结构探索(下)相关的知识,希望对你有一定的参考价值。
ios底层原理之类,元类数据结构探索(下)
一,class_rw_t
中firstSubclass
探索
(lldb) p/x GoodOne.class
(Class) $0 = 0x0000000100008398 GoodOne
(lldb) p/x 0x0000000100008398 + 0x20
(long) $1 = 0x00000001000083b8
(lldb) p (class_data_bits_t*)0x00000001000083b8
(class_data_bits_t *) $2 = 0x00000001000083b8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010069a990
(lldb) p *$3
(class_rw_t) $5 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000128
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p GoodTwo.class
(Class) $10 = GoodTwo
(lldb) p $2->data()
(class_rw_t *) $11 = 0x000000010069a990
(lldb) p *$11
(class_rw_t) $12 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000128
}
}
firstSubclass = GoodTwo
nextSiblingClass = NSUUID
}
我们通过打印可得出结论:GoodTwo
继承于 GoodOne
,当我们查看GoodOne 中的 class_rw_t 数据
时 firstSubclass = nil
这时他的子类GoodTwo 并没有加载
,当我们调用p GoodTwo.class
时 ,在打印可以发现 firstSubclass = GoodTwo
,所以我们有一个推测,类的加载是一种懒加载的形式
。
二,class_ro_t
中的baseProperties
探究
(lldb) p $12.ro()
(const class_ro_t *) $13 = 0x0000000100008040
(lldb) p *$13
(const class_ro_t) $14 = {
flags = 0
instanceStart = 8
instanceSize = 16
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "GoodOne" {
Value = 0x0000000100003ea9 "GoodOne"
}
}
baseMethodList = 0x0000000100008088
baseProtocols = nil
ivars = 0x00000001000080f0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008118
_swiftMetadataInitializer_NEVER_USE = {}
(lldb) p *$14.baseProperties
(property_list_t) $15 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 1)
}
(lldb) p $15.get(0)
(property_t) $16 = (name = "hobby", attributes = "T@\\"NSString\\",C,N,V_hobby")
}
通过内存偏移取值可以看到,baseProperties
中也是存放当前类的属性。
三, class_ro_t
中的ivars
探索
(lldb) p/x GoodOne.class
(Class) $0 = 0x0000000100008388 GoodOne
(lldb) p/x 0x0000000100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t*)0x00000001000083a8
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p *$2->data()
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000128
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p *$3.ro()
(const class_ro_t) $4 = {
flags = 0
instanceStart = 8
instanceSize = 24
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "GoodOne" {
Value = 0x0000000100003ec3 "GoodOne"
}
}
baseMethodList = 0x0000000100008088
baseProtocols = nil
ivars = 0x00000001000080f0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008138
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x00000001000080f0
(lldb) p *$5
(const ivar_list_t) $6 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 2)
}
(lldb) p $6.get(0)
(ivar_t) $7 = {
offset = 0x0000000100008368
name = 0x0000000100003eeb "count"
type = 0x0000000100003f79 "@\\"NSString\\""
alignment_raw = 3
size = 8
}
(lldb) p $6.get(1)
(ivar_t) $8 = {
offset = 0x0000000100008370
name = 0x0000000100003ef1 "_name"
type = 0x0000000100003f79 "@\\"NSString\\""
alignment_raw = 3
size = 8
}
(lldb)
通过打印我们可以看出原来类中的成员变量
信息都放在ivar_list_t
中。
四,成员变量 VS 实例变量VS 属性。
@interface GoodOne : NSObject{
NSString *count; 成员变量
NSObject *obg; 实例变量
}
@property (nonatomic, copy) NSString *name; 属性
结论:
属性 = 带下划线成员变量 + setter + getter 方法
这也是为什么ProperiesList中只有属性没有成员变量
,而IvarList中有对应的成员变量,和带下划线的成员变量。
实例变量 : 特殊的成员变量 (类的实例化)
六,setter方法在底层的实现原理
1,我们可以确认一点,setter方法本质就是对某一块内存赋值。那么不同的属性set方法是不一样的,那么不可能我们每一个不同的setter方法都在最底层去实现。所以这里就有一个中间层对所有的set方法做一层处理。然后在通过imp 重定向到setPropety 方法最后找到对应的内存存储区域。
@property (nonatomic, copy) NSArray *array1;
@property (nonatomic, strong) NSArray *array;
static void _I_Goods_setArray1_(Goods * self, SEL _cmd, NSArray * _Nonnull array1) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Goods, _array1), (id)array1, 0, 1); }
static NSArray * _Nonnull _I_Goods_array(Goods * self, SEL _cmd) { return (*(NSArray * _Nonnull *)((char *)self + OBJC_IVAR_$_Goods$_array)); }
static void _I_Goods_setArray_(Goods * self, SEL _cmd, NSArray * _Nonnull array) { (*(NSArray * _Nonnull *)((char *)self + OBJC_IVAR_$_Goods$_array)) = array; }
结论:copy修饰的属性使用objc_setProperty方式实现,其它属性使用内存偏移实现
七,类方法信息的储存位置
(lldb) p/x GoodOne.class
(Class) $0 = 0x00000001000083b0 GoodOne
(lldb) x/4gx GoodOne.class
0x1000083b0: 0x00000001000083d8 0x000000010036a140
0x1000083c0: 0x00000001007a6020 0x0002802800000007
(lldb) p/x 0x00000001000083d8 & 0x00007ffffffffff8
(long) $2 = 0x00000001000083d8
(lldb) p/x 0x00000001000083d8 + 0x20
(long) $3 = 0x00000001000083f8
(lldb) p (class_data_bits_t*)$3
(class_data_bits_t *) $4 = 0x00000001000083f8
(lldb) p *$4->data()
(class_rw_t) $5 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000432
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff802c5eb0
}
(lldb) p $5.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000081b8
}
arrayAndFlag = 4295000504
}
}
}
(lldb) p $6.list.ptr
(method_list_t *const) $7 = 0x00000001000081b8
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p *$8.get(0)
error: <user expression 10>:1:1: indirection requires pointer operand ('method_t' invalid)
*$8.get(0)
^~~~~~~~~~
(lldb) p $8.get(0)
(method_t) $9 = {}
(lldb) p $8.get(0).big()
(method_t::big) $10 = {
name = "stGood"
types = 0x0000000100003f8d "v16@0:8"
imp = 0x0000000100003cf0 (KCObjcBuild`+[GoodOne stGood])
}
结论:类方法存储在元类中。这可能也是为什么有元类这个概念的原因之一,对象的依附是类,那类的依附就是元类
补充(一) 针对2020年苹果对类的优化
先来了解两个概念
Clean Memory
clean memory
干净的不可更改的内存。
例如:class_ro_t
就属于clean memory
,因为它是只读
的
clean memory 的优点:可以通过系统调控,回收或者重新加载 ,从而节省更多的内存空间。
Dirty Memory
dirty memory:
不干净的,可随时更改内存。
例如:class_rw_t
就属于 dirty memory
,当进程运行时类的数据结构发生改变时 内存也相应的发生变化即成为 dirty memory
,
由于dirty memory
要比clean memory
昂贵的多,只要进行运行它就必须一直存在,所以苹果针对
class_rw_t
做了相关优化,将class_rw_t
中常用和非常用字段做了分离。将常用的字段放到class_rw_ext_t
中,这样class_rw_t
的内存空间节约将近一倍。
补充(二)类中的方法编码
{(struct objc_selector *)"setArray1:", "v24@0:8@16", (void *)_I_Goods_setArray1_},
v
表示:void返回类型。
24
表示:setArray1函数占用的字节数。
@
表示:参数,id self。
0
表示:从0号位置开始。
:
表示: SEL。
8
表示:从8号位置开始。
@
表示:参数,setArray1。
16
表示:从16号位置开始。
通过xcode查看编码图:打开xcode–> command+shift+0–> 搜索ivar_getTypeEncoding–> 点击Type Encodings
未完待续。。。
以上是关于iOS底层原理之类,元类数据结构探索(下)的主要内容,如果未能解决你的问题,请参考以下文章