Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint
示例代码
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
简单测试代码 :
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i<1000; i++) {
@autoreleasepool {
NSError *error;
NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-SW5"];
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
if (i == 999) {
NSLog(@"fileContents : %@", fileContents);
}
}
}
}
从这里可以总结考虑, 为了保持项目中的内存使用情况, 保持流畅, 在项目中的相关地方应该是用autoreleasepool
- 文件的读写处理
- 数据的批量处理, 包括图像裁剪生成, 音视频编解码
- 线程中block的回调处理
比如:
文件的读写处理
线程中block的回调处理
深入研究 :
通过clang指令把main.m转为main.cpp文件
cd到想要编译的文件目录
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
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);
}
main.cpp
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
查找 __AtAutoreleasePool __autoreleasepool;
__AtAutoreleasePool
创建 : objc_autoreleasePoolPush
销毁 : objc_autoreleasePoolPop
从源码中找 :
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage
NSObject-internal.h中的定义
NSObject.mm中查看实现
AutoreleasePoolPageData
NSObject-internal.h中的定义
那这里的 AutoreleasePoolPage 是什么东西呢?
一个空的 AutoreleasePoolPage 的内存结构如下图所示:
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
- magic 用来校验 AutoreleasePoolPage 的结构是否完整;
- next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
- thread 指向当前线程;
- parent 指向父结点,第一个结点的 parent 值为 nil ;
- child 指向子结点,最后一个结点的 child 值为 nil ;
- depth 代表深度,从 0 开始,往后递增 1;
- hiwat 代表 high water mark 。
另外,通过析构函数研究可以知道
当 next == begin() 时,表示 AutoreleasePoolPage 为空;
当 next == end() 时,表示 AutoreleasePoolPage 已满。
~AutoreleasePoolPage()
{
check();
unprotect();
ASSERT(empty());
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
ASSERT(!child);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
回到最初
代码块的实现逻辑如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
- 先通过调用objc_autoreleasePoolPush函数来创建一个AutoreleasePool对象。
- 然后给在代码块里创建的每个自动释放的对象发送一个autorelease消息,将这些自动释放的对象加入到AutoreleasePool对象里。
- 最后在AutoreleasePool对象将要销毁时,通过调用objc_autoreleasePoolPop函数给池中每个自动释放的对象发送一次release消息,再销毁AutoreleasePool对象。
注意区分AutoreleasePool对象和自动释放的对象,AutoreleasePool对象指的是实例化的一个自动释放池(本质也是对象),而 自动释放的对象是指被加入到这个池中的对象。
简单粗暴总结 :
- 注意理解AutoreleasePool本身是什么, AutoreleasePool和runloop的关系
- 使用AutoreleasePool, 在autoreleasePool里面的对象会在合适的时间段内释放, 什么是合适的时间段, 每一次runloop的时候,Entry(即将进入Loop)到Before waiting(准备进入休眠)会处理一次AutoreleasePool
- AutoreleasePool也是一个对象, 其他添加在AutoreleasePool中的对象本质做的是延迟释放, AutoreleasePool释放时统一给所有对象发送一次release消息。
回到最开始的代码
- 情况一:循环过程中,创建的NSString对象一直在堆积,只有在循环结束才一起释放,所以内存一直在增加。
- 情况二:每一次迭代中都会创建并销毁一个AutoreleasePool,而每一次创建的NSString对象都会加入到AutoreleasePool中,所以在每次创建AutoreleasePool都会调用AutoreleasePoolPage::tls_dealloc时,NSString对象就会被释放,这样内存就不会增加。
每一次循环 , 每一次创建的NSString对象都会加入到AutoreleasePool:
static void init()
{
int r __unused = pthread_key_init_np(AutoreleasePoolPage::key,
AutoreleasePoolPage::tls_dealloc);
ASSERT(r == 0);
}
AutoreleasePoolPage::tls_dealloc)
static void tls_dealloc(void *p)
{
if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
// No objects or pool pages to clean up here.
return;
}
// reinstate TLS value while we work
setHotPage((AutoreleasePoolPage *)p);
if (AutoreleasePoolPage *page = coldPage()) {
if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools
if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
// pop() killed the pages already
} else {
page->kill(); // free all of the pages
}
}
// clear TLS value so TLS destruction doesn't loop
setHotPage(nil);
}
参考:
Objective-C Autorelease Pool 的实现原理
自动释放池的前世今生 —- 深入解析 Autoreleasepool
ARC的原理详解
iOS原理 AutoreleasePool的基本概念