本文主要内容
一.内存布局
二.内存办理计划
三.数据结构
四.MRC&ARC
五.引证计数办理
六.弱引证办理
七.主动开释池
八.循环引证
一.内存布局
- stack: 栈区,办法调用
- heap: 堆区,经过alloc等分配的目标
- bss: 未初始化的全局变量等
- data: 已初始化的全局变量等
- text: 程序代码
二.内存办理计划
1、内存办理计划
- TaggedPointer: NSNumber等小目标
- NONPOINTER_ISA: 64位架构下应用程序,非指针型ISA
- 散列表: 复杂的数据结构,其间包含引证计数表、弱引证表
关于内存办理的评论全部依据objc-runtime-680版别解说
2、NONPOINTER_ISA办理计划
arm64架构
- indexed: [0]1位,0代表纯isa,只存储类目标地址,1代表isa,类地址和内存办理相关数据
- has_assoc: [1]1位, 当时目标是否有相关目标,0代表没有,1代表有
- has_cxx_dtor: [2]1位,当时目标是否运用C++相关内容
- shiftcls: [3,35]33位,当时目标类目标的指针地址
- magic: [36,41]6位
- weakly_referenced: [42]1位,标识目标是否有弱引证指针
- deallocating: [43]1位,是否在进行dealloc操作
- has_sidetable_rc: [44]1位,当时isa指针所存储的引证计数到达上限,需求外挂数据结构存储引证计数内容(散列表)
- extra_rc: [45,63]19位,额定的引证计数
3、散列表方式
- SideTables()结构-Hash结构
- SideTable结构
问题1:为什么不是一个SideTable?
- 假设只有一张SideTable表,相当于在内存中分配的一切目标的引证计数或弱引证存储都放在一张表中,假如要操作某个目标的引证计数值,进行加1或减1的修正,
因为一切目标可能是在不同的线程中分配创建
,包含调用release/retain办法也可能在不同线程操作,此刻对SideTable表的操作需求加锁处理才干保证线程安全,就会存在功率问题。
处理计划:
- 分离锁,处理功率问题。把内存目标所对应的引证计数表分拆成8个。
问题2:怎样完成快速分流?
也便是说经过一个目标的指针怎样快速定位到它归于哪张SideTable表。
-
SideTables的实质是一张Hash表
-
一个目标的指针作为Key经过Hash函数运算,核算出一个value值来决定出目标对应的SideTable是哪张或在数组哪个方位、索引是什么
-
Hash查找
给定值是目标内存地址,目标值是数组下标索引。 把目标内存地址作为函数参数,经过Hash函数的运算得出数组下标索引值。实际是经过目标内存地址和SideTables数组个数取余核算,核算出在哪张表中。Hash查找是为了进步查找功率,存储是经过Hash函数存储,查找也经过Hash查找,不需求遍历。
三.数据结构(散列表方式完成的内存办理计划)
- Spinlock_t:自旋锁
- RefcountMap: 引证计数表
- weak_table_t:弱引证表
1、Spinlock_t自旋锁
-
Spinlock_t是“忙等”的锁
。 - 假如当时锁已被其他线程获取,当时线程就会不断的探测这个锁是否被开释,假如开释第一时间获取当时锁。
- 正常的信号链,当获取不到锁时,会将所在线程阻塞休眠,比及其他线程开释该锁时唤醒当时线程。
适用于轻量拜访
2、RefcountMap引证计数表
-
即
Hash表
-
运用Hash查找,用于进步查找功率
。 -
为什么能进步查找功率?
因为在存储目标的引证计数时便是经过Hash函数核算存储方位的,而获取目标的引证计数值时也是经过这个Hash函数来获取索引方位,防止循环遍历的操作
。 -
size_t,64位
weakly_reference:第1位,是否有弱引证;
deallocating: 第2位,是否在进行dealloc操作; RC:引证计数值
3、弱引证表weak_table_t
- Hash表
四.MRC & ARC
1、什么是MRC?
- 经过
手动引证计数
来对目标进行内存办理 - alloc:分配内存空间
retain
:目标引证计数+1
release
:目标引证计数-1
retainCount
:获取目标引证计数值
autorelease
:调用autoreleasePool完毕时目标引证计数-1
dealloc:开释父类成员变量
2、什么是ARC?
- 经过
主动引证计数
来办理内存 - 是LLVM编译器和Runtime协作的成果
- 制止手动调用
retain/release/retainCount/dealloc
- 新增
weak
、strong
特点关键字
问题1:MRC和ARC的差异
- MRC是手动办理内存,ARC是由编译器和Runtime协作主动引证计数的内存办理;
- MRC中能够调用引证计数相关的办法,ARC中无法调用。
五.引证计数办理
完成原理分析
- alloc
- retain
- release
- retainCount
- dealloc
1、alloc完成
经过一系列调用,终究调用了C函数calloc。此刻并没有设置引证计数为1。
2、retain完成
- 经过
2次
哈希查找。 - 经过当时目标的指针到SideTables()去获取它所属的SideTable(
第1次Hash查找
),再在SideTable中获取引证计数值(第2次Hash查找
),再对这个引证计数值进行加1操作,因为存储引证计数的Size_t64个bit位中从第三位开始才存储引证计数,前两位没有存储引证计数,所以此刻加的是偏移量(4)。
3、release完成
- 经过
2次
哈希查找。 - 经过当时目标经过Hash算法在SideTables()中找到它所属的SideTable,然后依据当时目标指针拜访table的引证计数表查找引证计数值,然后对引证计数值进行减1操作,与retain完成同理,此刻减的也是一个偏移量。
4、retainCount完成
- 经过当时目标经过Hash算法在SideTables()中找到它所属的SideTable,声明size_t变量为1,依据当时目标指针拜访table的引证计数表查找引证计数值,把查找的成果向右偏移操作。假如刚alloc的目标,在引证计数表中没有该目标相相关的key/value映射,此刻it->second为0,因为局部变量refcnt_result为1,所以
经过alloc调用产生的目标调用retainCount值为1
。
5、dealloc完成
- 首要会调用
_obic_rootDealloc()
函数,接着调用rootDealloc()
函数 - 在
rootDealloc()
函数内部会进行是否能够开释的判别? 判别当时目标是否运用了非指针型的isa; 判别当时目标是否有弱引证 判别当时目标是否有相关目标 判别当时目标内部完成是否有C++内容以及是否以ARC办理内存 判别当时目标的引证计数是否用sideTable引证计数表来维护 () - 假如以上条件都不满意,即没有非指针型isa、没有弱引证、没有相关目标、内部没有C++内容并且不是以ARC办理内存、没有sideTable表维护引证计数,则调用C函数free()
- 否则调用
object_dispose()
5.1 object dispose()完成
- 调用
_objc_destructinstance()
函数,毁掉实例 - 再调用C函数free()
5.2 obj destructinstance()完成
- 先判别当时目标内部是否有C++相关内容或是否选用ARC;
- 假如有,调用
object _cxxDestruct()
函数; - 假如没有,会判别当时目标是否有相关目标,假如当时目标有相关目标,会调用
_obiect_remove_assocations()
来移除目标相关的相关目标; - 假如当时目标没有相关目标,会调用
clearDeallocating()
。
问题:经过相关目标技术为类添加了实例变量,在目标的dealloc办法中是否有必要对相关目标进行移除操作?
- 在体系的dealloc内部完成中,会主动判别是否有相关目标,有的话,体系内部就会把相关的相关目标移除。
5.3 clearDeallocating()完成
- 首要调用
sidetable clearDeallocating()
函数; - 然后调用
weak_clear_no_lock()
函数,将指向该目标的弱引证指针置为nil; - 再调用
table.refcnts.erase()
函数,从引证计数表中擦除该目标引证计数。
问题:假如一个目标有weak指针指向它,当该目标dealloc或抛弃后,它的weak指针为何被置为nil?
-
dealloc
内部完成中做客将指向该目标的弱引证指针置为nil的操作
六.弱引证办理
问题:weak变量怎样被添加到弱引证表中的
- 一个被声明为__weak的目标指针,经过编译器编译会调用相应的
objc_initWeak()
函数,经过一系列的函数调用栈,终究在weak_register_no_lock()
函数傍边做弱引证变量的添加,添加方位由Hash算法查找,假如查找的方位已经有了当时目标对应的弱引证数组,就把新的弱引证变量添加到数组中,假如没有,从头创建一个弱引证数组,第0个方位添加上最新的weak
指针,其他方位都初始化为0或nil。
问题:当一个目标被开释或抛弃后,weak变量怎样处理
- 铲除weak变量,一起设置指向为nil
- 为何会被主动置为nil?当一个目标被dealloc()之后,在dealloc()内部完成中,会调用弱引证铲除的相关函数,在这个函数内部会依据当时目标指针查找弱引证表,把当时目标相对应的弱引证(数组)都拿出来,遍历数组中的弱引证指针分被置为nil。
七.主动开释池
- 编译器会将
@autoreleasepool{}
改写为:
void *ctx = objc_autoreleasePoolPush();
{ objc_autoreleasePoolPop(ctx)}
-
objc_autoreleasePoolPush()
函数
void *objc_autoreleasePoolPush(void)
// 内部调用如
void *AutoreleasePoolPage::push(void)
-
objc_autoreleasePoolPop(ctx)
函数 一次pop实际上相当于一次批量的pop操作
void objc_autoreleasePoolPop(void *ctx)
//内部调用
AutoreleasePoolPage::pop(void *ctx)
1、回忆知识点
双向链表
首要头节点的ParentPtr
(父指针)指向空NULL,后续的各个节点ParentPtr
指向前一个节点,ChildPtr
指向它的后一个节点,最后一个节点的ChildPt r
指向空NULL。
双向链表的结构:
栈
栈是向下添加的,下面是高地址,上面是低地址。对栈的操作有出栈、入栈,特点是后入先出
。
栈的结构:
2、AutoreleasePoolPage(C++类)
- id* next:指向栈中下一个可填充的方位
- AutoreleasePoolPage* const parent; :父指针
- AutoreleasePoolPage* child; :子指针
- pthread_t const thread; :线程
AutoreleasePoolPage的结构:
向下添加。最下面为AutoreleasePoolPage
自身占用内存,向上能够存储{}
中填充的的Auto release
目标。
AutoreleasePoolPage::push的内部完成
产生push操作后,会把当时next
方方位为nil,称为岗兵目标
,将next指针指向下一个可入栈的方位。实际上每次进行AutoreleasePool
代码块的创建,相当于不断向栈中刺进岗兵目标
。
3、[obj autorelease]完成进程
当一个目标调用autorelease
,首要要判别当时next
是否指向栈顶
,假如没有指向栈顶
,就将此目标直接添加到当时栈的next指向的方位。假如指向栈顶
,需求添加一个栈节点到链表上
,然后在新的栈上添加目标。
AutoreleasePoolPage::pop的内部完成
- 依据传入的岗兵目标找到对应方位
- 给上次push操作之后添加的目标依次发送release消息
- 问题1:array的内存是在什么时分开释的?
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *array = [NSMutableArray array];
NSLog(@"%@",array);
}
答1:在当次runloop将要完毕的时分调用`AutoreleasePoolPage::pop()
- 问题2:
AutoreleasePool
为何能够嵌套运用?
答4:多层嵌套便是多次刺进岗兵目标。每次进行`AutoreleasePool`代码块的创建,相当于不断向栈中刺进`岗兵目标`。
-
问题3:什么是主动开释池/主动开释池完成结构?
是以`栈`为结点经过双向链表的方式组合而成; 是和`线程`一一对应的。
-
问题3:什么情况下需求手动创建
AutoreleasePool
?在for循环中alloc图片数据等内存耗费较大的场景需求手动刺进`AutoreleasePool`
-
问题4:
AutoreleasePool
的完成原理是怎样的?
`AutoreleasePool`的完成原理便是以栈为节点、经过双向链表方式组合而成的数据结构。
八.循环引证
三种循环引证
- 自循环引证
- 彼此循环引证
- 多循环引证
1、循环引证类型
自循环引证
假设有一个目标,目标中有一个成员变量obj,目标强持有成员变量obj,假如将成员变量赋值为原目标,则会形成自循环引证。
彼此循环引证
有一个目标A,其间有一个id类型成员变量obj,有另一个目标B,其间有一个id类型成员变量obj,假如目标A中的obj指向目标B,一起目标B中的obj指向目标A,则会形成彼此循环引证
多循环引证
存在多个目标,每一个目标的obj都指向下一个目标,则会形成多循环引证。
2.循环引证的要点(考点)
-
署理
-
Block
-
NSTimer
-
大环引证
-
问题1:怎样破除循环引证?
(1)防止产生循环引证 (2)在适宜的机遇手动断环
-
问题2:处理循环引证的计划有哪些?
(1)__weak
(2)__block
- MRC下,__block润饰目标不会添加其引证计数,防止了循环引证
- ARC下,__block润饰目标会被强引证,无法防止循环引证,需求手动解环
(3)__unsafe_unretained
- 润饰目标不会添加其引证计数,防止了循环引证
- 假如被润饰目标在某一机遇被开释,会产生悬垂指针!(因此不建议运用)
3.循环引证示例
NSTimer循环引证问题
问题:在日常开发中是否遇到过循环引证的问题?怎样处理循环引证的?
遇到NSTimer的循环引证问题。处理计划是:创建一个中间目标,令中间目标别离持有两个弱引证目标NSTimer、原目标,在NSTimer中直接分派的回调在中间目标完成,在中间目标完成NSTimer回调的办法中,对它所持有的Target进行值得判别,假如值存在,将NSTimer的回调给原目标,假如当时目标已经被开释,设置NSTimer为无效状态,就免除了当时线程Runloop对NSTimer的强引证、以及NSTimer对当时目标的强引证。
本文总结
问题1:什么是ARC?
ARC是由LLVM编译器和Runtime一起协作为完成主动引证计数办理。
问题2:为什么weak指针指向的目标在抛弃之后会被主动置为nil?
当目标被抛弃之后,dealloc办法的内部完成中会调用对弱引证铲除的办法,在铲除弱引证办法中,会经过哈希算法查找被抛弃目标在弱引证表傍边的方位来提取它所对应的弱引证指针的列表数组,然后进行for循环遍历将一切weak指针置为nil。
问题3:苹果是怎样完成AutoreleasePool的?
AutoreleasePool
是以栈
为节点、经过双向链表
方式来合成的数据结构。
问题4:什么是循环引证?你遇到过哪些循环引证,是怎样处理的?
见八.循环引证
有任何问题,欢迎各位评论指出!觉得博主写的还不错的费事点个赞喽