1.探索
首先通过po [NSRunloop currentRunloop]
在控制台可以看到以下打印信息,发现runloop与autoreleasepool有关系
- 我们可以推测,在触发obsever监听的时候,会处理autoreleasepool
- order表示runloop处理的优先级
- 我们通过activities = 0xa0,可以监听autoreleasepool的处理
2.监听autoreleasepool
char autoreleaseActive = 0xa0;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), autoreleaseActive, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"_wrapRunLoopWithAutoreleasePoolHandler");
});
- 通过实践,在一次runloop的周期中大概会执行5次。
-
添加一个信号断点
- 发现在闲置状态(beforeWating)会不时进入这个断点
3.从runloop源码探究
- 虽然autoreleasepool的处理一直被触发,但是并没有唤醒休眠状态的runloop
4. autoreleasepool源码
提问:arc有必要使用autoreleasepool吗?
- 在mrc的时代,往往创建一个autoreleasepool对象
- arc则使用
@ autoreleasepool
提问:arc下使用@ autoreleasepool 的意义?
- 首先执行下面两段代码作为对比
for (int i = 0; i < 1024*1024*30; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"0123456789"];
};
}
for (int i = 0; i < 1024*1024*30; i++) {
NSString *str = [NSString stringWithFormat:@"0123456789"];
}
- 结论:
第一段代码在会对内存进行优化,释放速度快;
第二段代码造成内存大量堆积,释放缓慢 - 使用场景:当程序有大量中间临时变量产生时,避免内存使用峰值过高,及时释放内存的场景
通过clang解读@autoreleasepool源码
- 命令
clang -rewrite-objc
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;
};
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"0123456789"];
};
约等于
创建了一个autorelease对象 push autorelease
NSString *str = [NSString stringWithFormat:@"0123456789"];
释放掉autorelease(pop ~) clang
- 但是从clang出的源码中我们看不出释放,是因为autorelease的释放使用c++中的析构
5. autoreleasepool底层运行原理
- AutoreleasePoolPage实例
magic_t const magic; //用来校验 AutoreleasePoolPage 的结构是否完整
id *next;//指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()
pthread_t const thread;//指向当前线程
AutoreleasePoolPage * const parent;// 指向父结点,第一个结点的 parent 值为 nil
AutoreleasePoolPage *child;//指向父结点,第一个结点的 parent 值为 nil
uint32_t const depth;//链表的深度,节点个数
uint32_t hiwat;//high water mark 数据容纳的一个上限
PAGE_MAX_SIZE;//size大小为4096,虚拟内存每个扇区4096个字节,4K对齐的说法
COUNT;//一个page里的对象数
POOL_BOUNDARY;//边界对象,以前为POOL_SENTINEL哨兵对象
(AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组成)
- 当我们添加一个需要被Autorelease的对象
string1
magic_t const magic;
id *next;
NSString *string1;
pthread_t const thread;
AutoreleasePoolPage * const parent;的 parent 值为 nil
AutoreleasePoolPage *child;值为 nil
uint32_t const depth;
uint32_t hiwat;
PAGE_MAX_SIZE;
COUNT;
POOL_BOUNDARY;
- 再添加
string2
、string3
内部会变成
magic_t const magic;
id *next;
NSString *string3;
NSString *string2;
NSString *string1;
pthread_t const thread;
AutoreleasePoolPage * const parent;的 parent 值为 nil
AutoreleasePoolPage *child;值为 nil
uint32_t const depth;
uint32_t hiwat;
PAGE_MAX_SIZE;
COUNT;
POOL_BOUNDARY;
这个操作是由id *next
实现的,它在有新的对象加入AutoreleasePoolPage时,会自动指向下一个地址
- 当添加到上限时,新的对象就会加入到下一个page,所以构成了双向链表的结构
magic_t const magic; magic_t const magic;
id *next; id *next;
..... NSString *string49;
NSString *...; NSString *string50;
NSString *...; pthread_t const thread;
NSString *string3; ......
NSString *string2; ......
NSString *string1;
pthread_t const thread;
AutoreleasePoolPage * const
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
PAGE_MAX_SIZE;
COUNT;
POOL_BOUNDARY;
Page 1 page2
总结
- AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组成
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
- iOS里的TaggedPointer不适用autorelesepool
- NSAutoreleasePool可以创建一个autorelease pool,但该对象本身也需要被释放 drain
- 在ARC下,不能使用上述方式调用autorelease,而应当使用@autoreleasepool{}
- 对于不同线程,应当创建自己的autorelease pool。如果应用长期存在,应该定期drain和创建新的autorelease pool。
- runloop 与 AutoreleasePool 是协同合作关系
- AutoreleasePool 与 runloop 与线程是一一对应的关系
- AutoreleasePool 在 runloop 在开始时被push,在runloop休眠时(beforewaiting状态)pop