iOS内存管理.docx
《iOS内存管理.docx》由会员分享,可在线阅读,更多相关《iOS内存管理.docx(21页珍藏版)》请在冰豆网上搜索。
![iOS内存管理.docx](https://file1.bdocx.com/fileroot1/2023-4/17/b94451e3-2d63-4190-8a40-fd0f55a9efcf/b94451e3-2d63-4190-8a40-fd0f55a9efcf1.gif)
iOS内存管理
iOS进阶iOS内存管理
我将在本篇博文中详细的从ARC解释到iOS的内存管理,以及Block相关的原理、源码。
作者:
佚名来源:
iOS大全|2017-03-0710:
15
收藏
分享
1似乎每个人在学习iOS过程中都考虑过的问题
∙allocretainreleasedelloc做了什么?
∙autoreleasepool是怎样实现的?
∙__unsafe_unretained是什么?
∙Block是怎样实现的
∙什么时候会引起循环引用,什么时候不会引起循环引用?
所以我将在本篇博文中详细的从ARC解释到iOS的内存管理,以及Block相关的原理、源码。
2从ARC说起
说iOS的内存管理,就不得不从ARC(AutomaticReferenceCounting/自动引用计数)说起,ARC是WWDC2011和iOS5引入的变化。
ARC是LLVM3.0编译器的特性,用来自动管理内存。
与Java中GC不同,ARC是编译器特性,而不是基于运行时的,所以ARC其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。
ARC的内存管理规则可以简述为:
∙每个对象都有一个『被引用计数』
∙对象被持有,『被引用计数』+1
∙对象被放弃持有,『被引用计数』-1
∙『引用计数』=0,释放对象
3你需要知道
∙包含NSObject类的Foundation框架并没有公开
∙CoreFoundation框架源代码,以及通过NSObject进行内存管理的部分源代码是公开的。
∙GNUstep是Foundation框架的互换框架
GNUstep也是GNU计划之一。
将CocoaObjective-C软件库以自由软件方式重新实现
某种意义上,GNUstep和Foundation框架的实现是相似的
通过GNUstep的源码来分析Foundation的内存管理
4allocretainreleasedealloc的实现
4.1GNU–alloc
查看GNUStep中的alloc函数。
GNUstep/modules/core/base/Source/NSObject.malloc:
1.+ (id) alloc
2.
3.{
4.
5.return [self allocWithZone:
NSDefaultMallocZone()];
6.
7.}
8.
9.
10.+ (id) allocWithZone:
(NSZone*)z
11.
12.{
13.
14.return NSAllocateObject (self, 0, z);
15.
16.}
GNUstep/modules/core/base/Source/NSObject.mNSAllocateObject:
1.struct obj_layout {
2.
3.NSUInteger retained;
4.
5.};
6.
7.
8.
9.NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
10.
11.{
12.
13.int size = 计算容纳对象所需内存大小;
14.
15.id new = NSZoneCalloc(zone, 1, size);
16.
17.memset (new, 0, size);
18.
19.new = (id)&((obj)new)[1];
20.
21.}
NSAllocateObject函数通过调用NSZoneCalloc函数来分配存放对象所需的空间,之后将该内存空间置为nil,最后返回作为对象而使用的指针。
我们将上面的代码做简化整理:
GNUstep/modules/core/base/Source/NSObject.malloc简化版本:
1.struct obj_layout {
2.
3.NSUInteger retained;
4.
5.};
6.
7.
8.
9.+ (id) alloc
10.
11.{
12.
13.int size = sizeof(struct obj_layout) + 对象大小;
14.
15.struct obj_layout *p = (struct obj_layout *)calloc(1, size);
16.
17.return (id)(p+1)
18.
19.return [self allocWithZone:
NSDefaultMallocZone()];
20.
21.}
alloc类方法用structobj_layout中的retained整数来保存引用计数,并将其写入对象的内存头部,该对象内存块全部置为0后返回。
一个对象的表示便如下图:
4.2GNU–retain
GNUstep/modules/core/base/Source/NSObject.mretainCount:
1.- (NSUInteger) retainCount
2.
3.{
4.
5.return NSExtraRefCount(self) + 1;
6.
7.}
8.
9.
10.inline NSUInteger
11.
12.NSExtraRefCount(id anObject)
13.
14.{
15.
16.return ((obj_layout)anObject)[-1].retained;
17.
18.}
GNUstep/modules/core/base/Source/NSObject.mretain:
1.- (id) retain
2.
3.{
4.
5.NSIncrementExtraRefCount(self);
6.
7.return self;
8.
9.}
10.
11.
12.
13.inline void
14.
15.NSIncrementExtraRefCount(id anObject)
16.
17.{
18.
19.if (((obj)anObject)[-1].retained == UINT_MAX - 1)
20.
21.[NSException raise:
NSInternalInconsistencyException
22.
23.format:
@"NSIncrementExtraRefCount() asked to increment too far”];
24.
25.((obj_layout)anObject)[-1].retained++;
26.
27.}
以上代码中,NSIncrementExtraRefCount方法首先写入了当retained变量超出最大值时发生异常的代码(因为retained是NSUInteger变量),然后进行retain++代码。
4.3GNU–release
和retain相应的,release方法做的就是retain--。
GNUstep/modules/core/base/Source/NSObject.mrelease
1.- (oneway void) release
2.
3.{
4.
5.if (NSDecrementExtraRefCountWasZero(self))
6.
7.{
8.
9.[self dealloc];
10.
11.}
12.
13.}
14.
15.
16.
17.BOOL
18.
19.NSDecrementExtraRefCountWasZero(id anObject)
20.
21.{
22.
23.if (((obj)anObject)[-1].retained == 0)
24.
25.{
26.
27.return YES;
28.
29.}
30.
31.((obj)anObject)[-1].retained--;
32.
33.return NO;
34.
35.}
4.4GNU–dealloc
dealloc将会对对象进行释放。
GNUstep/modules/core/base/Source/NSObject.mdealloc:
1.- (void) dealloc
2.
3.{
4.
5.NSDeallocateObject (self);
6.
7.}
8.
9.
10.inline void
11.
12.NSDeallocateObject(id anObject)
13.
14.{
15.
16.obj_layout o = &((obj_layout)anObject)[-1];
17.
18.free(o);
19.
20.}
4.5Apple实现
在Xcode中设置Debug->DebugWorkflow->AlwaysShowDisassenbly打开。
这样在打断点后,可以看到更详细的方法调用。
通过在NSObject类的alloc等方法上设置断点追踪可以看到几个方法内部分别调用了:
retainCount
1.__CFdoExternRefOperation
2.CFBasicHashGetCountOfKey
retain
1.__CFdoExternRefOperation
2.CFBasicHashAddValue
release
1.__CFdoExternRefOperation
2.CFBasicHashRemoveValue
可以看到他们都调用了一个共同的__CFdoExternRefOperation方法。
该方法从前缀可以看到是包含在CoreFoundation,在CFRuntime.c中可以找到,做简化后列出源码:
CFRuntime.c__CFDoExternRefOperation:
1.int __CFDoExternRefOperation(uintptr_t op, id obj) {
2.
3.CFBasicHashRef table = 取得对象的散列表(obj);
4.
5.int count;
6.
7.
8.
9.switch (op) {
10.
11.case OPERATION_retainCount:
12.
13.count = CFBasicHashGetCountOfKey(table, obj);
14.
15.return count;
16.
17.break;
18.
19.case OPERATION_retain:
20.
21.count = CFBasicHashAddValue(table, obj);
22.
23.return obj;
24.
25.case OPERATION_release:
26.
27.count = CFBasicHashRemoveValue(table, obj);
28.
29.return 0 == count;
30.
31.}
32.
33.}
所以__CFDoExternRefOperation是针对不同的操作,进行具体的方法调用,如果op是OPERATION_retain,就去掉用具体实现retain的方法。
从BasicHash这样的方法名可以看出,其实引用计数表就是散列表。
key为hash(对象的地址)value为引用计数。
下图是Apple和GNU的实现对比:
5autorelease和autorelaesepool
在苹果对于NSAutoreleasePool的文档中表示:
每个线程(包括主线程),都维护了一个管理NSAutoreleasePool的栈。
当创先新的Pool时,他们会被添加到栈顶。
当Pool被销毁时,他们会被从栈中移除。
autorelease的对象会被添加到当前线程的栈顶的Pool中。
当Pool被销毁,其中的对象也会被释放。
当线程结束时,所有的Pool被销毁释放。
对NSAutoreleasePool类方法和autorelease方法打断点,查看其运行过程,可以看到调用了以下函数:
1.NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2.
3.// 等同于 objc_autoreleasePoolPush
4.
5.
6.
7.id obj = [[NSObject alloc] init];
8.
9.[obj autorelease];
10.
11.// 等同于 objc_autorelease(obj)
12.
13.
14.
15.[NSAutoreleasePool showPools];
16.
17.// 查看 NSAutoreleasePool 状况
18.
19.
20.
21.[pool drain];
22.
23.// 等同于 objc_autoreleasePoolPop(pool)
[NSAutoreleasePoolshowPools]可以看到当前线程所有pool的情况:
1.objc[21536]:
##############
2.
3.objc[21536]:
AUTORELEASE POOLS for thread 0x10011e3c0
4.
5.objc[21536]:
2 releases pending.
6.
7.objc[21536]:
[0x101802000] ................ PAGE (hot) (cold)
8.
9.objc[21536]:
[0x101802038] ################ POOL 0x101802038
10.
11.objc[21536]:
[0x101802040] 0x1003062e0 NSObject
12.
13.objc[21536]:
##############
14.
15.Program ended with exit code:
0
在objc4中可以查看到AutoreleasePoolPage:
1.objc4/NSObject.mm AutoreleasePoolPage
2.
3.
4.
5.class AutoreleasePoolPage
6.
7.{
8.
9.static inline void *push()
10.
11.{
12.
13.生成或者持有 NSAutoreleasePool 类对象
14.
15.}
16.
17.static inline void pop(void *token)
18.
19.{
20.
21.废弃 NSAutoreleasePool 类对象
22.
23.releaseAll();
24.
25.}
26.
27.static inline id autorelease(id obj)
28.
29.{
30.
31.相当于 NSAutoreleasePool 类的 addObject 类方法
32.
33.AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;
34.
35.}
36.
37.id *add(id obj)
38.
39.{
40.
41.将对象追加到内部数组
42.
43.}
44.
45.void releaseAll()
46.
47.{
48.
49.调用内部数组中对象的 release 方法
50.
51.}
52.
53.};
54.
55.
56.
57.void *
58.
59.objc_autoreleasePoolPush(void)
60.
61.{
62.
63.if (UseGC) return nil;
64.
65.return AutoreleasePoolPage:
:
push();
66.
67.}
68.
69.
70.
71.void
72.
73.objc_autoreleasePoolPop(void *ctxt)
74.
75.{
76.
77.if (UseGC) return;
78.
79.AutoreleasePoolPage:
:
pop(ctxt);
80.
81.}
AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)。
thread指针指向当前线程。
每个AutoreleasePoolPage对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。
next指针指向下一个add进来的autorelease的对象即将存放的位置。
一个Page的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表。
6__unsafe_unretained
有时候我们除了__weak和__strong之外也会用到__unsafe_unretained这个修饰符,那么我们对__unsafe_unretained了解多少?
__unsafe_unretained是不安全的所有权修饰符,尽管ARC的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
赋值时即不获得强引用也不获得弱引用。
来运行一段代码:
1.id __unsafe_unretained obj1 = nil;
2.
3.{
4.
5.id __strong obj0 = [[NSObject alloc] init];
6.
7.
8.obj1 = obj0;
9.
10.
11.NSLog(@"A:
%@", obj1);
12.
13.}
14.
15.
16.NSLog(@"B:
%@", obj1);
运行结果:
1.2017-01-12 19:
24:
47.245220 __unsafe_unretained[55726:
4408416] A:
2.
3.2017-01-12 19:
24:
47.246670 __unsafe_unretained[55726:
4408416] B:
4.
5.Program ended with exit code:
0
对代码进行详细分析:
1.id __unsafe_unretained obj1 = nil;
2.
3.{
4.
5.// 自己生成并持有对象
6.
7.id __strong obj0 = [[NSObject alloc] init];
8.
9.
10.
11.// 因为 obj0 变量为强引用,
12.
13.// 所以自己持有对象
14.
15.obj1 = obj0;
16.
17.
18.
19.// 虽然 obj0 变量赋值给 obj1
20.
21.// 但是 obj1 变量既不持有对象的强引用,也不持有对象的弱引用
22.
23.NSLog(@"A:
%@", obj1);
24.
25.// 输出 obj1 变量所表示的对象
26.
27.}
28.
29.
30.
31.NSLog(@"B:
%@", obj1);
32.
33.// 输出 obj1 变量所表示的对象
34.
35.// obj1 变量表示的对象已经被废弃
36.
37.// 所以此时获得的是悬垂指针
38.
39.// 错误访问
所以,最后的NSLog只是碰巧正常运行,如果错误访问,会造成crash
在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符变量时,要确保对象确实存在
【编辑推荐】
1.iOS抓取HTML,CSSXPath解析数据
2.iOS与Android开发——我们该如何选择?
3.用Jenkins搭建iOS/Android持续集成打包平台
4.iOS10个实用小技巧(总有你不知道的和你会用到的)
5.iOS进阶——Block