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(); @autoreleasepool { _objc_autoreleasePoolPrint(); HTPerson *p1 = [[[HTPerson alloc] init] autorelease]; HTPerson *p2 = [[[HTPerson alloc] init] autorelease]; _objc_autoreleasePoolPrint(); } _objc_autoreleasePoolPrint(); 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. objc[68122 ]: [0x102802000 ] ................ PAGE (hot) (cold) objc[68122 ]: [0x102802038 ] ################ POOL 0x102802038 objc[68122 ]: ############## objc[68122]: ############## (print3) objc[68122 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68122 ]: 3 releases pending. objc[68122 ]: [0x102802000 ] ................ PAGE (hot) (cold) objc[68122 ]: [0x102802038 ] ################ POOL 0x102802038 objc[68122 ]: [0x102802040 ] 0x100704a10 HTPerson objc[68122 ]: [0x102802048 ] 0x10075cc30 HTPerson objc[68122 ]: ############## objc[68156]: ############## (print4) objc[68156 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68156 ]: 0 releases pending. 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(); @autoreleasepool { _objc_autoreleasePoolPrint(); HTPerson *p1 = [[[HTPerson alloc] init] autorelease]; HTPerson *p2 = [[[HTPerson alloc] init] autorelease]; _objc_autoreleasePoolPrint(); @autoreleasepool { HTPerson *p3 = [[[HTPerson alloc] init] autorelease]; _objc_autoreleasePoolPrint(); @autoreleasepool { HTPerson *p4 = [[[HTPerson alloc] init] autorelease]; _objc_autoreleasePoolPrint(); } _objc_autoreleasePoolPrint(); } _objc_autoreleasePoolPrint(); } _objc_autoreleasePoolPrint(); 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. objc[68285 ]: [0x102802000 ] ................ PAGE (hot) (cold) objc[68285 ]: [0x102802038 ] ################ POOL 0x102802038 objc[68285 ]: ############## objc[68285]: ############## (print3) objc[68285 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68285 ]: 3 releases pending. 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]: ############## (print4) objc[68285 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68285 ]: 5 releases pending. 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]: ############## (print5) objc[68285 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68285 ]: 7 releases pending. 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 ]: [0x102802060 ] ################ POOL 0x102802060 objc[68285 ]: [0x102802068 ] 0x100551880 HTPerson objc[68285 ]: ############## objc[68285]: ############## (print6) objc[68285 ]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68285 ]: 5 releases pending. 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. 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. 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 { for (int i = 0 ; i < 600 ; i++) { HTPerson *p = [[[HTPerson alloc] init] autorelease]; } @autoreleasepool { for (int i = 0 ; i < 500 ; i++) { HTPerson *p = [[[HTPerson alloc] init] autorelease]; } @autoreleasepool { for (int i = 0 ; i < 200 ; i++) { HTPerson *p = [[[HTPerson alloc] init] autorelease]; } _objc_autoreleasePoolPrint(); } } } 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. objc[69731 ]: [0x100806000 ] ................ PAGE (full) (cold) objc[69731 ]: [0x100806038 ] ################ POOL 0x100806038 objc[69731 ]: [0x100806040 ] 0x10182a040 HTPerson objc[69731 ]: [0x100806048 ] ..................... objc[69731 ]: [0x100806ff8 ] 0x101824e40 HTPerson objc[69731 ]: [0x102806000 ] ................ PAGE (full) objc[69731 ]: [0x102806038 ] 0x101824e50 HTPerson objc[69731 ]: [0x102806040 ] ..................... objc[69731 ]: [0x102806330 ] 0x101825440 HTPerson objc[69731 ]: [0x102806338 ] ################ POOL 0x102806338 objc[69731 ]: [0x102806340 ] 0x101825450 HTPerson objc[69731 ]: [0x102806348 ] ..................... objc[69731 ]: [0x1028067e0 ] 0x101825d90 HTPerson objc[69731 ]: [0x102804000 ] ................ PAGE (hot) objc[69731 ]: [0x102804038 ] 0x101826dd0 HTPerson objc[69731 ]: [0x102804040 ] ..................... objc[69731 ]: [0x102804310 ] 0x101827380 HTPerson objc[69731 ]: [0x102804318 ] ################ POOL 0x102804318 objc[69731 ]: [0x102804320 ] 0x101827390 HTPerson objc[69731 ]: [0x102804328 ] ..................... objc[69731 ]: [0x102804958 ] 0x10182b160 HTPerson objc[69731 ]: ##############
iOS 工程示例分析 从以上macOS
工程示例可以得知,在@autoreleasepool
大括号结束的时候,就会调用Page
的pop()
方法,给@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 int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { appDelegateClassName = NSStringFromClass ([AppDelegate class ]); } return UIApplicationMain (argc, argv, nil , appDelegateClassName); } int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain (argc, argv, nil , NSStringFromClass ([AppDelegate class ])); } }
如果你的程序使用了AppKit
或UIKit
框架,那么主线程的RunLoop
就会在每次事件循环迭代中创建并处理@autoreleasepool
。也就是说,应用程序所有autorelease
对象的都是由RunLoop
创建的@autoreleasepool
来管理。而main()
函数中的@autoreleasepool
只是负责管理它的作用域中的autorelease
对象。
在以上《使用 MacOS 工程示例分析》章节中提到了嵌套@autoreleasepool
的情况。Xcode旧版本
的main
函数中是将整个应用程序运行(UIApplicationMain
)放在@autoreleasepool
内,而主线程
的RunLoop
就是在UIApplicationMain
中创建,所以RunLoop
创建的@autoreleasepool
是嵌套在main
函数的@autoreleasepool
内的。RunLoop
会在每次事件循环中对自动释放池进行pop
和push
(以下会详细讲解),但是它的pop
只会释放掉它的POOL_BOUNDARY
之后的对象,它并不会影响到外层即main
函数中@autoreleasepool
。
新版本 Xcode 11 中的 main 函数发生了哪些变化?
旧版本
是将整个应用程序运行放在@autoreleasepool
内,由于RunLoop
的存在,要return
即程序结束后@autoreleasepool
作用域才会结束,这意味着程序结束后main
函数中的@autoreleasepool
中的autorelease
对象才会释放。
而在 Xcode 11
中,触发主线程RunLoop
的UIApplicationMain
函数放在了@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__); }
可以看到,调用了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__); }
添加进手动指定的@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'
参考
iOS - 聊聊 autorelease 和 @autoreleasepool