内存管理
iOS内存管理总结
iOS的内存管理方案
- iOS对于小对象(类似
NSNumber
、NSIndexPath
)等,使用的是TaggedPointer- 在arm64位架构下,因为指针不需用到所有64位,为了节约内存,使用了NonPointer_ISA(非指针形的isa,主要利用isa剩余空间)指针
- 散列表,其中包含了引用计数表和弱引用表
TaggedPointer
iOS中的小对象的内存管理方式
NONPOINTER_ISA
数字指针位数
- 最低位(Indexed标志位)如果为0则就是纯指针的isa,1的话就是非指针形isa指针
- 是否有关联对象
- 是否有C++相关
- 33位表达isa指针
- magic 这个不重要
- 是否有弱引用指针
- 是否在dealloc操作
- 是否有外挂的引用计数SideTable(超过一定数量的引用计数将在外面外挂一个此散列表来存储引用计数)
- 额外引用计数
SideTables
SideTables
包含多个SideTable
graph BT; E(weak_table_t)-->A(SideTable); D(RefCountMap)-->A(SideTable); C(spinklock_t)-->A(SideTable); A(SideTable)-->B(SideTables); E1(weak_table_t)-->A1(SideTable); D1(RefCountMap)-->A1(SideTable); C1(spinklock_t)-->A1(SideTable); A1(SideTable)-->B;
为什么是多个SideTable组成SideTables
主要是为了解决效率问题. 当程序创建成千上万个对象的时候, 当调用引用计数改变的时候, 可能会在不同的线程中, 这个时候就需要加锁, 如果所有对象都在一个
SideTable
中, 就会存在效率问题.
系统如果解决这种效率问题
引入了分离锁的概念. 假如建立8个这样的
SideTable
, 对应就是8个锁, 将所有对象分配到不同表中, 这样在不同表中的对象就可以做到并发操作, 同一个表中进行顺序操作. 提高访问效率
如何快速分流(如何快速定位一个对象在那个SideTable表中)
SideTable
他本质是一个Hash
表, 可以使用对象的指针地址, 经过Hash
函数, 找到目标对象的索引值
graph LR; A(对象指针地址) --Hash函数--> B(SideTable);
1 |
|
spinlock_t
使用因为优先级翻转问题被苹果抛弃了. 解释详情OSSpinLock
自旋锁, 是一种忙等锁, 线程反复检查锁变量是否可用, 不会被挂起. 避免了进程上下文的调度开销,适合阻塞很短时间的场合.
iOS 10以后替换为os_unfair_lock
, 它是一个互斥锁, 他保存这线程中的所有权信息. 在加锁后, 处于线程休眠的状态.
RefcountMap 引用计数表
本质也是一个Hash表.
根据对象指针地址, 经过一个伪装(用来躲避一些监测工具如Leak)的操作, 获取对象的引用计数.
graph LR; A(ptr) --DisguisedPtr传入objc_object --> B(size_t);
- 是否有弱引用
- 是否在deallocating
- RC 剩下保存引用计数值(向右偏移2位)
weak_table_t
根据对象指针找到
weak_entry_t
, 进行存储
graph LR; A(ptr) --Hash函数 --> B(weak_entry_t);
graph BT; B(weak_entry_t); C(weakPrt) --> B; D(weakPrt) --> B; E(weakPrt) --> B; F(...) --> B;
MRC
什么是MRC
通过手动引用计数来管理对象的引用计数
1 |
|
alloc 原理
调用alloc, 分配内存(内存对齐16), 此时并没有给对象的引用计数赋值为1
graph TD; A(alloc)-->B(_objc_rootAlloc); B-->C(callAlloc); C-->D(calloc);
retain
经过两次Hash函数找到对象的引用计数值+1
1 |
|
release
和
retain
正好相反
retainCount
1 |
|
dealloc
- 对象释放
graph TD; tip(提示)-.虚线是小几率.->Z(slowpath); A(dealloc)-->B(_objc_rootDealloc); B-->C(rootDealloc); C-->D{是否直接释放?}; E(nonpointer)--是-->D; F(weakly_referenced)--否-->D; I(has_cxx_dtor)--否-->D; G(has_assoc)--否-->D; H(has_sidetable_rc)--否-->D; D--是-->K(free); D-.否.->L(object_dispose); L-->M(objc_destructInstance); M-->K;
- C++和关联对象处理
graph TD; A(objc_destructInstance)-->B{hasCxxDtor}; B-.是.->C(object_cxxDestruct); B--否-->D{hasAssociatedObject}; D-.是.->E(_object_remove_assocations); D--否-->F[clearDeallocating]; C --> D; E --> F;
- 引用计数处理,
weak_clear_no_lock
将弱引用自动置为nil;table->refcnts->erase
擦除对应额外的引用计数
graph TD; A(clearDeallocating)-->B{是否是指针型isa}; B-.是.->C(sidetable_clearDeallocating); C-->G1(weak_clear_no_lock); G1-->H1(table->refcnts->erase); H1-->F[结束]; B--否-->D{是否含有弱引用或者额外引用计数表}; D-.是.->E(clearDeallocating_slow); E-->J{是否有弱引用}; J--是-->G(weak_clear_no_lock); J--否-->K{是否有额外引用计数表}; K--是-->H(table->refcnts->erase); K--否-->F; G-->K; H-->F; D--否-->F;
ARC
什么是ARC
自动引用计数管理内存.
ARC是由编译器自动插入retain
和Release
, 并且是由LLVM
和Runtime
共同协作的结果.
弱引用管理
创建
给一个对象添加弱引用
graph TD; A(objc_initWeak)-->B(storeWeak); B-->C{weak_register_no_lock}; C--有-->D(通过Hash找到weak_entry_t添加进入); C--没有-->G(新建一个weak_entry_t);
释放
当一个对象被调到用dealloc之后,那么在内部实现当中会去调用它weak_clear_no_lock的函数, 去执行清除相关操作,在函数中会根据当前对象指针查找到弱引用表,把当前对象相对应的所有弱引用取出来, 是一个数组,遍历该数组将对象置为nil.
graph TD; A(dealloc)-->B(weak_clear_no_lock); B-->C(找到所有的weak_referrer_t); C-->D(遍历将对象置为nil); D-->F(从weak_table_t删除weak_entry_t);
AutoreleasePool
以栈为节点通过双向链表形式组合而成, 并且和线程一一对应.
释放时机: 当前
Runloop
将要结束的时候调用AutorepleasePoolPage::Pop
, 给所有对象发送release
嵌套调用相当于增加对象到Page中
@autoreleasePool
1 |
|
graph TD; A(objc_autoreleasePoolPush)-->B(AutorepleasePoolPage::Push); C(objc_autoreleasePoolPop)-->D(AutorepleasePoolPage::Pop);
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!