一、@autoreleasepool
到底是干什么的?
使用clang -rewrite-objc main.m
将
@autoreleasepool {
}
翻译成C++文件可知
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return 0;
}
}
全局搜索可以得到
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
看代码可知其实是个结构体,上面的代码可以写成
atautoreleasepoolobj = objc_autoreleasePoolPush()
...
objc_autoreleasePoolPop(atautoreleasepoolobj)
二、那objc_autoreleasePoolPush
和objc_autoreleasePoolPop
又是什么呢?
查看runtime的objc4-646目录下的NSObject.mm
源代码可以知道具体的实现
AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
- magic用来校验AutoreleasePoolPage结构是否完整;
- next指向第一个可用的地址;
- thread指向当前的线程;
- parent指向父类
- child指向子类
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
这两个方法快速获得可用的地址范围
每一个autoreleasepool
都是由一个或多个AutoreleasePoolPage
的双向链表组成的
static size_t const SIZE PAGE_MAX_SIZE; // size and alignment, power of 2
static size_t const COUNT = SIZE / sizeof(id);
展开就是
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_MAX_SIZE PAGE_SIZE
所以:AutoreleasePoolPage的大小都是一样的4096
关于AutoreleasePoolPage的数据结构借用一下Draveness的图
大致搞清楚了autoreleasepool
的结构我们再下面从autoreleasepool
的创建和对象如何加到autoreleasepool
来具体说说中间都发生了什么
三、autoreleasepool
的创建
当我们使用@autoreleasepool { }的时候,由上面知道实际是先调用了objc_autoreleasePoolPush
方法,我们先看看objc_autoreleasePoolPush的实现
void *objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
展开其实就是
static inline void *push()
{
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
相信大家都看到了autoreleaseFast()这个方法
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
这个方法就是AutoreleasePoolPage具体初始化的地方,看实现知道有三种情况
- hotPage存在并且容量没有满,直接添加对象
- hotPage存在但是容量已经满了,调用autoreleaseFullPage方法,初始化一个AutoreleasePoolPage并把page传入,并标记为hotPage;
- hotPage不存在,调用autoreleaseNoPage创建一个AutoreleasePoolPage,并标记为hotePage,并且添加一个POOL_SENTINEL(哨兵对象)
上面的autoreleaseFast方法就是创建autoreleasepool的核心实现
另外看到这个关键字了吧inline
,其实就是内联函数,可以提供执行速度,可以看看我之前的文章
总结:每次push会产生一个新的autoreleasepool,并生成一个POOL_SENTINEL
四、对象是如何加到autoreleasepool中去的
说完autoreleasepool的创建,接下来说对象是如何加到autoreleasepool中去的,当对象调用【object autorelease】的方法的时候就会加到autoreleasepool中
+ (id)autorelease {
return (id)self;
}
// Replaced by ObjectAlloc
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
展开为
inline id
objc_object::rootAutorelease()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
if (fastAutoreleaseForReturn((id)this)) return (id)this;
return rootAutorelease2();
}
再展开
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
其实就是AutoreleasePoolPage的autorelease方法
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || *dest == obj);
return obj;
}
是不是觉得似曾相识?是的,和之前的push方法几乎一样,就是入参不一样。
-
push
方法传的是POOL_SENTINEL
-
autorelease
方法是传的具体需要autoRelease对象
细心的同学肯定发现了assert(!obj->isTaggedPointer());
这个断言里面的isTaggedPointer
了吧,这个是苹果后面为了提高内存利用率推出来的伪指针
,要想深入了解可以点击此处。
对比完autoreleasepool
的创建和对象如何加到autoreleasepool
我们可以得出下面的结论:
- 只有push操作会产生
POOL_SENTINEL
; -
POOL_SENTINEL
的个数就是autoreleasepool
的个数,实际开发中会有嵌套使用的情况。
五、说完添加接着来说说移除pop
当执行到这个objc_autoreleasePoolPop
方法的时候
autoreleasepool
会向POOL_SENTINEL
地址后面的对象都发release
消息,直到第一个POOL_SENTINEL
对象截止。
我们来理解一下这个图:
- 首先看到有两个
POOL_SENTINEL
说明autoreleasepool
还嵌套了一个autoreleasepool
,当最新的autoreleasepool
执行objc_autoreleasePoolPop
方法的时候,最右边的AutoreleasePoolPage
向里面的对象发release
消息直到中间那个POOL_SENTINEL
为止 - 由
coldPage
方法的定义可知最左边的那个是coldPage
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
依次类推。。。
六、实践
理论说完了,究竟在什么地方用这个呢,看苹果官方文档说
- 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
- 如果你编写的循环中创建了大量的临时对象;
- 如果你创建了一个辅助线程。
官方提供了一个例子
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. */
}
}
这里特地写了一个demo测试这个
static const int kStep = 50000;
static const int kIterationCount = 10 * kStep;
//查看app运行内存
- (void)obserMemoryUsage{
NSNumber *num = nil;
NSString *str = nil;
for (int i = 0; i < kIterationCount; i++) {
@autoreleasepool {
num = [NSNumber numberWithInt:i];
str = [NSString stringWithFormat:@"打哈萨克的哈克实打实的哈克时间的话大声疾呼多阿萨德爱仕达按时 "];
//Use num and str...whatever...
[NSString stringWithFormat:@"%@%@", num, str];
if (i % kStep == 0) {
double ff = getMemoryUsage();
NSLog(@"%f",ff);
}
}
}
}
double getMemoryUsage(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
return memoryUsageInMB;
}
统一操作了3次我们对比一下内存占用情况
加了 @autoreleasepool {}
控制台
不加 @autoreleasepool {}
同样是3次加了 @autoreleasepool {}内存稳定在73左右,而不加 @autoreleasepool {}的会出现暴增的情况。
@autoreleasepool {}平时不怎么关心,仔细研究起来还是挺有用处的。
reference: