iOS底层原理之类,元类数据结构探索(下)

iOS底层原理之类,元类数据结构探索(下)

一,class_rw_tfirstSubclass探索

(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

clear memory是指加载后不会发生更改的内存
例如:class_ro_t 就属于clean memory,因为它是只读
clean memory 的优点:可以通过系统调控,回收或者重新加载 ,从而节省更多的内存空间。

Dirty Memory

通过heap Xcode | egrep 'class_rw|COUNT’我们可以看一下class_rw_t和class_rw_ext_t在Xcode中的占用
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的内存空间节约将近一倍。
运行时需要追踪每个类的更多的信息,所以当一个类首次被使用,运行时会为它分配额外的存储容量,也就是class_rw_t,用于读取-编写数据。在class_rw_t中存储了只有运行时才会生成的新信息,例如,所有的类都会链接成一个树状结构,通过First Subclass和Next Sibling Class指针实现,这允许运行时遍历当前使用的所有类,这对于使方法缓存无效非常有用。
当类第一次从磁盘加载到内存时,它们像上图这样开始,但是一旦使用它们便会更改。Swift类和Objective-C类共享此基础结构,因此每个Swift类也具有这些数据结构。
要了解随后发生的情况,了解干净内存和脏内存之间的区别很有用。
类结构一经使用就会变为dirty memory,因为动行时会向它写入新的数据。dirty memory比clear memory要昂贵得多,只要进程在运行,它就必须一直存在;另一方面,clear memory可以进行移除从而节省更多的运行空间,因为如果你需要clear memory系统可以从磁盘中重新加载。macOS可以选择换出dirty memory,但是因为iOS不使用Swap,所以dirty memory在iOS中代价很大。dirty memory是类数据分为两部分的原因。

那么,为什么当Mehods、Properties、Protocols在class_ro_t中已经存在,还要在class_rw_t中设计呢?因为它们可以在运行时进行更改,当category被加载时它可以向类中添加新的方法,而且程序员可以通过使用运行时API动态地添加它们。因为class_ro_t是只读的,所以我们需要在class_rw_t中追踪这些东西。现在这样做会占用相当多的内存,在任何给定的设备中都有很多类在使用,但是通过检查实际设备上的使用情况,发现大约只有10%的类真正的更改了它们的方法,而且只有Swift类会使用Demangled Name字段并且Swift类并不需要这个字段,除非访问它们的Object-C名称时才需要,所以我们可以拆掉那些平时不用的部分,这将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

未完待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值