Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint
Many programs create temporary objects that are autoreleased. These objects add to the program’s memory footprint until the end of the block. In many situations, allowing temporary objects to accumulate until the end of the current event-loop iteration does not result in excessive overhead; in some situations, however, you may create a large number of temporary objects that add substantially to memory footprint and that you want to dispose of more quickly. In these latter cases, you can create your own autorelease pool block. At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint.
The following example shows how you might use a local autorelease pool block in a for loop
示例代码
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
使用@autoreleasepool
从这里可以总结考虑, 为了保持项目中的内存使用情况, 保持流畅, 在项目中的相关地方应该是用autoreleasepool
- 文件的读写处理
- 数据的批量处理, 包括图像裁剪生成, 音视频编解码
- 线程中block的回调处理
比如:
文件的读写处理
文件的读写处理线程中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;
image.png__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中的定义
AutoreleasePoolPage
NSObject.mm中查看实现
AutoreleasePoolPage
AutoreleasePoolPageData
NSObject-internal.h中的定义
image.png
那这里的 AutoreleasePoolPage 是什么东西呢?
NSObject.mm相关源码一个空的 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);
}
if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools
调用了objc_autoreleasePoolPop
参考:
Objective-C Autorelease Pool 的实现原理
自动释放池的前世今生 —- 深入解析 Autoreleasepool
ARC的原理详解
iOS原理 AutoreleasePool的基本概念
网友评论