学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS内存管理之AutoreleasePool

autoreleasePool 示例

mac OS 工程示例分析

iOS工程中,系统在自动释放池中注册了一些对象。为了排除这些干扰,接下来我们通过macOS工程代码示例,结合AutoreleasePoolPage的内存分布图以及_objc_autoreleasePoolPrint()私有函数,来帮助我们更好地理解@autoreleasepool的原理

注意:

由于ARC环境下不能调用autorelease等方法,所以需要将工程切换为MRC环境。
如果使用ARC,则可以使用__autoreleasing所有权修饰符替代autorelease方法。

单个 @autoreleasepool

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool {
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
}
_objc_autoreleasePoolPrint(); // print4
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 自动释放池的情况
objc[68122]: ############## (print1)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 0 releases pending. //当前自动释放池中没有任何对象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: ##############

objc[68122]: ############## (print2)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 1 releases pending. //当前自动释放池中有1个对象,这个对象为POOL_BOUNDARY
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: ##############

objc[68122]: ############## (print3)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 3 releases pending. //当前自动释放池中有3个对象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: [0x102802040] 0x100704a10 HTPerson //p1
objc[68122]: [0x102802048] 0x10075cc30 HTPerson //p2
objc[68122]: ##############

objc[68156]: ############## (print4)
objc[68156]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68156]: 0 releases pending. //当前自动释放池中没有任何对象,因为@autoreleasepool作用域结束,调用pop方法释放了对象
objc[68156]: [0x100810000] ................ PAGE (hot) (cold)
objc[68156]: ##############

嵌套 @autoreleasepool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
HTPerson *p3 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
HTPerson *p4 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 自动释放池的情况
objc[68285]: ############## (print1)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //当前自动释放池中没有任何对象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############

objc[68285]: ############## (print2)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 1 releases pending. //当前自动释放池中有1个对象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: ##############

objc[68285]: ############## (print3)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //当前自动释放池中有3个对象(1个@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: ##############

objc[68285]: ############## (print4)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //当前自动释放池中有5个对象(2个@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: ##############

objc[68285]: ############## (print5)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 7 releases pending. //当前自动释放池中有7个对象(3个@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: [0x102802060] ################ POOL 0x102802060 //POOL_BOUNDARY
objc[68285]: [0x102802068] 0x100551880 HTPerson //p4
objc[68285]: ##############

objc[68285]: ############## (print6)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //当前自动释放池中有5个对象(第3个@autoreleasepool已释放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: [0x102802050] ################ POOL 0x102802050
objc[68285]: [0x102802058] 0x1005065b0 HTPerson
objc[68285]: ##############

objc[68285]: ############## (print7)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //当前自动释放池中有3个对象(第2、3个@autoreleasepool已释放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: ##############

objc[68285]: ############## (print8)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //当前自动释放池没有任何对象(3个@autoreleasepool都已释放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############

复杂情况 @autoreleasepool

自动释放池(即所有的AutoreleasePoolPage对象)是以栈为结点通过双向链表的形式组合而成。每当Page满了的时候,就会创建一个新的Page,并设置它为hotPage,而首个Page为coldPage。接下来我们来看一下多个Page和多个@autoreleasepool嵌套的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}

一个AutoreleasePoolPage对象的内存大小为4096个字节,它自身成员变量占用内存56个字节,所以剩下的4040个字节用来存储autorelease对象的内存地址。又因为64bit下一个OC对象的指针所占内存为8个字节,所以一个Page可以存放505个对象的地址。POOL_BOUNDARY也是一个对象,因为它的值为nil。所以以上代码的自动释放池内存分布图如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //当前自动释放池中有1303个对象(3个POOL_BOUNDARY和1300个HTPerson实例)
objc[69731]: [0x100806000] ................ PAGE (full) (cold) /* 第一个PAGE,full代表已满,cold代表coldPage */
objc[69731]: [0x100806038] ################ POOL 0x100806038 //POOL_BOUNDARY
objc[69731]: [0x100806040] 0x10182a040 HTPerson //p1
objc[69731]: [0x100806048] ..................... //...
objc[69731]: [0x100806ff8] 0x101824e40 HTPerson //p504
objc[69731]: [0x102806000] ................ PAGE (full) /* 第二个PAGE */
objc[69731]: [0x102806038] 0x101824e50 HTPerson //p505
objc[69731]: [0x102806040] ..................... //...
objc[69731]: [0x102806330] 0x101825440 HTPerson //p600
objc[69731]: [0x102806338] ################ POOL 0x102806338 //POOL_BOUNDARY
objc[69731]: [0x102806340] 0x101825450 HTPerson //p601
objc[69731]: [0x102806348] ..................... //...
objc[69731]: [0x1028067e0] 0x101825d90 HTPerson //p1008
objc[69731]: [0x102804000] ................ PAGE (hot) /* 第三个PAGE,hot代表hotPage */
objc[69731]: [0x102804038] 0x101826dd0 HTPerson //p1009
objc[69731]: [0x102804040] ..................... //...
objc[69731]: [0x102804310] 0x101827380 HTPerson //p1100
objc[69731]: [0x102804318] ################ POOL 0x102804318 //POOL_BOUNDARY
objc[69731]: [0x102804320] 0x101827390 HTPerson //p1101
objc[69731]: [0x102804328] ..................... //...
objc[69731]: [0x102804958] 0x10182b160 HTPerson //p1300
objc[69731]: ##############

iOS 工程示例分析

从以上macOS工程示例可以得知,在@autoreleasepool大括号结束的时候,就会调用Pagepop()方法,给@autoreleasepool中的autorelease对象发送release消息, 在iOS工程中,方法里的autorelease对象是什么时候释放的呢?有系统干预释放手动干预释放两种情况。

系统干预释放是不指定@autoreleasepool,所有autorelease对象都由主线程的RunLoop创建的@autoreleasepool来管理。
手动干预释放就是将autorelease对象添加进我们手动创建的@autoreleasepool中。

系统干预释放

  • Xcode 11 版本的iOS程序中的main()函数,和旧版本的差异
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // Xcode 11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

// Xcode 旧版本
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

如果你的程序使用了AppKitUIKit框架,那么主线程的RunLoop就会在每次事件循环迭代中创建并处理@autoreleasepool。也就是说,应用程序所有autorelease对象的都是由RunLoop创建的@autoreleasepool来管理。而main()函数中的@autoreleasepool只是负责管理它的作用域中的autorelease对象。

在以上《使用 MacOS 工程示例分析》章节中提到了嵌套@autoreleasepool的情况。Xcode旧版本main函数中是将整个应用程序运行(UIApplicationMain)放在@autoreleasepool内,而主线程RunLoop就是在UIApplicationMain中创建,所以RunLoop创建的@autoreleasepool是嵌套在main函数的@autoreleasepool内的。RunLoop会在每次事件循环中对自动释放池进行poppush(以下会详细讲解),但是它的pop只会释放掉它的POOL_BOUNDARY之后的对象,它并不会影响到外层即main函数中@autoreleasepool

  • 新版本 Xcode 11 中的 main 函数发生了哪些变化?

旧版本是将整个应用程序运行放在@autoreleasepool内,由于RunLoop的存在,要return即程序结束后@autoreleasepool作用域才会结束,这意味着程序结束后main函数中的@autoreleasepool中的autorelease对象才会释放。

而在 Xcode 11中,触发主线程RunLoopUIApplicationMain函数放在了@autoreleasepool外面,这可以保证@autoreleasepool中的autorelease对象在程序启动后立即释放。正如新版本的@autoreleasepool中的注释所写 “Setup code that might create autoreleased objects goes here.”(如上代码),可以将autorelease对象放在此处。

  • 系统干预释放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)viewDidLoad {
[super viewDidLoad];
HTPerson *person = [[[HTPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}

// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[HTPerson dealloc]
// -[ViewController viewDidAppear:]

可以看到,调用了autorelease方法的person对象不是在viewDidLoad方法结束后释放,而是在viewWillAppear方法结束后释放,说明在viewWillAppear方法结束的时候,调用了pop()方法释放了person对象。其实这是由RunLoop控制的,下面来讲解一下RunLoop和@autoreleasepool的关系。

  • RunLoop 与 @autoreleasepool

iOS在主线程的RunLoop中注册了两个Observer

第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer

① 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()
② 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

在iOS工程中系统干预释放的autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放
以上person对象在viewWillAppear方法结束后释放,说明viewDidLoad和viewWillAppear方法在同一次循环里。

kCFRunLoopEntry:在即将进入RunLoop时,会自动创建一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。

kCFRunLoopBeforeWaiting:在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。然后创建一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()

kCFRunLoopBeforeExit,在即将退出RunLoop时,会自动销毁最后一个创建的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()

手动干预释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
HTPerson *person = [[[HTPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}

// -[HTPerson dealloc]
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[ViewController viewDidAppear:]

添加进手动指定的@autoreleasepool中的autorelease对象,在@autoreleasepool大括号结束时就会释放,不受RunLoop控制

相关问题

ARC 环境下,autorelease 对象在什么时候释放?

分系统干预释放和手动干预释放两种情况回答。

ARC 环境下,需不需要手动添加 @autoreleasepool?

AppKit 和 UIKit 框架会在RunLoop每次事件循环迭代中创建并处理@autoreleasepool,因此,你通常不必自己创建@autoreleasepool

苹果给出了三种需要手动添加@autoreleasepool的情况:
① 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
②如果你编写的循环中创建了大量的临时对象;
你可以在循环内使用@autoreleasepool在下一次迭代之前处理这些对象。在循环中使用@autoreleasepool有助于减少应用程序的最大内存占用。
③ 如果你创建了辅助线程。
一旦线程开始执行,就必须创建自己的@autoreleasepool;否则,你的应用程序将存在内存泄漏。

如果对 NSAutoreleasePool 对象调用 autorelease 方法会发生什么情况?
1
2
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool autorelease];

抛出异常NSInvalidArgumentException并导致程序Crash,异常原因:不能对NSAutoreleasePool对象调用autorelease。

1
2
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '*** -[NSAutoreleasePool autorelease]: Cannot autorelease an autorelease pool'

参考

  1. iOS - 聊聊 autorelease 和 @autoreleasepool