内存管理

iOS内存管理总结

iOS的内存管理方案

  1. iOS对于小对象(类似NSNumberNSIndexPath)等,使用的是TaggedPointer
  2. 在arm64位架构下,因为指针不需用到所有64位,为了节约内存,使用了NonPointer_ISA(非指针形的isa,主要利用isa剩余空间)指针
  3. 散列表,其中包含了引用计数表和弱引用表

TaggedPointer

iOS中的小对象的内存管理方式

NONPOINTER_ISA

数字指针位数

  1. 最低位(Indexed标志位)如果为0则就是纯指针的isa,1的话就是非指针形isa指针
  2. 是否有关联对象
  3. 是否有C++相关
  4. 33位表达isa指针
  5. magic 这个不重要
  6. 是否有弱引用指针
  7. 是否在dealloc操作
  8. 是否有外挂的引用计数SideTable(超过一定数量的引用计数将在外面外挂一个此散列表来存储引用计数)
  9. 额外引用计数

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
f(prt) = (uniptr_t)prt % array.count

spinlock_t

使用OSSpinLock自旋锁, 是一种忙等锁, 线程反复检查锁变量是否可用, 不会被挂起. 避免了进程上下文的调度开销,适合阻塞很短时间的场合. 因为优先级翻转问题被苹果抛弃了. 解释详情
iOS 10以后替换为os_unfair_lock, 它是一个互斥锁, 他保存这线程中的所有权信息. 在加锁后, 处于线程休眠的状态.

RefcountMap 引用计数表

本质也是一个Hash表.

根据对象指针地址, 经过一个伪装(用来躲避一些监测工具如Leak)的操作, 获取对象的引用计数.

graph LR;
    A(ptr) --DisguisedPtr传入objc_object --> B(size_t);
  1. 是否有弱引用
  2. 是否在deallocating
  3. 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
2
3
4
5
6
// 常用引用计数函数
alloc
retain
rataincount
release
autorelease

alloc 原理

调用alloc, 分配内存(内存对齐16), 此时并没有给对象的引用计数赋值为1

graph TD;
    A(alloc)-->B(_objc_rootAlloc);
    B-->C(callAlloc);
    C-->D(calloc);

retain

经过两次Hash函数找到对象的引用计数值+1

1
2
3
4
5
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}

release

retain正好相反

retainCount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];

size_t refcnt_result = 1;

table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
/// 对象为空 默认就是1
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}

dealloc

  1. 对象释放
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;
  1. 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;
  1. 引用计数处理, 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是由编译器自动插入retainRelease, 并且是由LLVMRuntime共同协作的结果.

弱引用管理

创建

给一个对象添加弱引用

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
2
3
4
5
@autoreleasePool {} 
//解析
void *pool objc_autoreleasePoolPush();
{} 中代码
objc_autoreleasePoolPop(pool);
graph TD;
    A(objc_autoreleasePoolPush)-->B(AutorepleasePoolPage::Push);
    C(objc_autoreleasePoolPop)-->D(AutorepleasePoolPage::Pop);

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!