一、介绍
autoreleasepool 自动释放池,在池子里的对象如果没有被强引用都会自动释放掉,自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage,调用了autorelease的对象最终都是通过 AutoreleasePoolPage 对象来管理的。
二、源码分析 - clang重写 @autoreleasepool
先关闭ARC,Build Setting 搜索 Objective-C Automatic Reference Counting Yes 改为 No,使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 命令生成C++代码。
main.m代码如下:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc]init]autorelease];
}
return 0;
}
main.cpp对应代码如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
__AtAutoreleasePool 结构体对应的代码如下:
/// struct 有点类似 Class
struct __AtAutoreleasePool {
/// 构造函数 创建对象
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
/// 析构函数 销毁对象
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
/// 声明的对象(接收构造函数生成的对象,也是传入析构函数的)
void * atautoreleasepoolobj;
};
一个@autoreleasepool的 { 和 } 相当于创建和销毁,整个作用域就在大括号内,那么其实开始的main.m里面的代码相当于如下代码:
int main(int argc, char * argv[]) {
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc]init]autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
return 0;
}
三、源码分析 AutoreleasePoolPage类对象
想要知道objc_autoreleasePoolPush和Pop做了什么,那就得去看看objc4源码里面具体做了啥,可以看到Push是调用了 AutoreleasePoolPage 类的 push() 方法,反之就是 pop()方法,所以自动释放池就是 AutoreleasePoolPage 对象来管理的。
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 类大概有600多行代码,截取部分代码展示如下:
class AutoreleasePoolPage
{
magic_t const magic;
/// 指向下一个可放对象的地址
id *next;
/// 在固定的一个线程执行,线程一一对应,线程之间的释放池是分开的
pthread_t const thread;
/// 链表父指针,指向上一个Page对象
AutoreleasePoolPage * const parent;
/// 链表子指针,指向下一个Page对象
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0) {
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
/// 以前这个SIZE = PAGE_MAX_SIZE = PAGE_SIZE = 4096
/// 现在好像是SIZE = PAGE_MAX_SIZE = 1 << 14,就是二进制的100000000000000 十进制的1乘以2的14次方,即2的14次方 = 16384,还是说我下载的[obj4](https://opensource.apple.com/tarballs/objc4/)代码版本不对,望知道的大佬告诉我一下
return (id *) ((uint8_t *)this+SIZE);
}
~AutoreleasePoolPage()
{
check();
unprotect();
assert(empty());
assert(!child);
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
void releaseAll()
{
releaseUntil(begin());
}
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
void kill()
{
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
public:
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
/// 方法实现省略
static inline void *push() {}
static inline void pop(void *token) {}
static void init() {}
void print() {}
static void printAll() {}
static void printHiwat()
};
上面有个 end() 方法可以知道每个 AutoreleasePoolPage 对象的大小,以前这个SIZE = PAGE_MAX_SIZE = PAGE_SIZE = 4096,现在好像是SIZE = PAGE_MAX_SIZE = 1 << 14,就是二进制的100000000000000 十进制的1乘以2的14次方,即2的14次方 = 16384,还是说我下载的obj4代码版本不对,望知道的大佬告诉我一下,以下就先按照4096来讲。
每个 AutoreleasePoolPage 对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease 对象(调用了autorelease方法的对象,如上面的person对象)的地址所有的 AutoreleasePoolPage 对象通过双向链表的形式连接在一起,第一个对象的parent指向的是空,child指向下一个对象,下一个对象的parent指向上一个对象,child指向下一个对象,这样循环最后一个对象的child指向是空,这就是双向链表结构。
假设第一个 AutoreleasePoolPage 对象的起始地址是 0x1000,那么结束地址就是 0x2000,16进制换算一下 0x1000 = 4096,自己所带的7个成员变量占56个字节,所以能够存放 autorelease 对象的地址就是0x1038~0x2000,也可以通过 begin()方法算出开始地址是多少。
四、 objc_autoreleasePoolPush,objc_autoreleasePoolPop,autorelease 方法做了什么
AutoreleasePoolPage 类对象中*next 指向了下一个能存放 autorelease 对象地址的区域 。
调用 objc_autoreleasePoolPush 方法会将一个 POOL_BOUNDARY 入栈(数据结构算是栈结构),并且返回其存放的内存地址。
调用 objc_autoreleasePoolPop 方法时传入一个 POOL_BOUNDARY 的内存地址,会从最后一个入栈的对象调用对象的 release 方法,直到遇到这个 POOL_BOUNDARY,可以在上面的部分源码 releaseAll()方法里面查看到。
对象的 autorelease 方法 通过断点看汇编代码发现方法实际是调用了 objc_autorelease() 方法,把对象添加到Page对象中去,通过objc4源码可以看到调用顺序如下。
1、obj->autorelease()
2、rootAutorelease()
3、rootAutorelease2()
4、AutoreleasePoolPage类的autorelease()
5、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);
}
}
objc_autoreleasePoolPush,objc_autoreleasePoolPop
int main(int argc, char * argv[]) {
@autoreleasepool {
for (int i = 0; i<1000; i++) {
Person *person = [[[Person alloc]init]autorelease];
}
}
/**上面代码 翻译过来基本就是下面这样*/
/// 将 POOL_BOUNDARY 入栈 返回地址其实就是 0x1038 因为第一个可存放对象的地址就是它
atautoreleasepoolobj = objc_autoreleasePoolPush();
// atautoreleasepoolobj = 0x1038;
/// 循环将 person 对象放入到 AutoreleasePoolPage 对象去,不够就新建一个Page对象child指针指向
for (int i = 0; i<1000; i++) {
Person *person = [[[Person alloc]init]autorelease];
}
/// 将 0x1038 传入,从栈顶开始释放对象,一直释放到 0x1038 对象为止
objc_autoreleasePoolPop(atautoreleasepoolobj);
// objc_autoreleasePoolPop(0x1038);
return 0;
}
如果是 autoreleasepool 嵌套,其实也是一样的,没有用完一个page对象的空间不会再生成一个新的page对象,而是在原对象中继续压入POOL_BOUNDARY来区分。
int main(int argc, char * argv[]) {
/// 创建一个 AutoreleasePoolPage 对象
@autoreleasepool { /// pool_boundary_1 = objc_autoreleasePoolPush(), POOL_BOUNDARY 压进去
/// person1 对象 压入
Person *person1 = [[[Person alloc]init]autorelease];
/// person2 对象 压入
Person *person2 = [[[Person alloc]init]autorelease];
/// 没有超出 4096 字节,不用再创建 AutoreleasePoolPage 对象
@autoreleasepool { /// pool_boundary_2 = objc_autoreleasePoolPush(), POOL_BOUNDARY 再压进去
/// person3 对象 压入
Person *person3 = [[[Person alloc]init]autorelease];
/// person4 对象 压入
Person *person4 = [[[Person alloc]init]autorelease];
} /// objc_autoreleasePoolPop(pool_boundary_2), 从栈顶开始释放到 pool_boundary_2 为止
/// person5 对象 压入
Person *person5 = [[[Person alloc]init]autorelease];
/// person6 对象 压入
Person *person6 = [[[Person alloc]init]autorelease];
} /// objc_autoreleasePoolPop(pool_boundary_1), 从栈顶开始释放到 pool_boundary_1 为止
return 0;
}
五、通过 _objc_autoreleasePoolPrint 方法查看自动释放池的具体情况
在 objc4 源码的 NSObject.mm 文件中有一个 _objc_autoreleasePoolPrint 方法,实际调用了 AutoreleasePoolPage的打印输出方法,我们来看看能看到哪些信息。
void _objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
例子代码如下
/// 需要先这样定义一下方法名,这样才能调用,runtime才会去Foundation框架去找实现的方法
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person1 = [[[Person alloc]init]autorelease];
Person *person2 = [[[Person alloc]init]autorelease];
_objc_autoreleasePoolPrint();
@autoreleasepool {
Person *person3 = [[[Person alloc]init]autorelease];
Person *person4 = [[[Person alloc]init]autorelease];
}
}
return 0;
}
打印如下,
- 3 release pending 是因为第一个是Push压入的 POOL_BOUNDARY 所以 POOL 0x7fe4e380e038 代表的就是,然后才是person1,person2对象。
- PAGE (hot) (cold) 代表的就是一个 AutoreleasePoolPage 对象,(hot) 代表的是当前正在使用的
objc[10164]: ##############
objc[10164]: AUTORELEASE POOLS for thread 0x103d34600
objc[10164]: 3 releases pending.
objc[10164]: [0x7fe4e380e000] ................ PAGE (hot) (cold)
objc[10164]: [0x7fe4e380e038] ################ POOL 0x7fe4e380e038
objc[10164]: [0x7fe4e380e040] 0x6000020440c0 Person
objc[10164]: [0x7fe4e380e048] 0x6000020440d0 Person
objc[10164]: ##############
再换一个例子
/// 需要先这样定义一下方法名,这样才能调用,runtime才会去Foundation框架去找实现的方法
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person1 = [[[Person alloc]init]autorelease];
Person *person2 = [[[Person alloc]init]autorelease];
@autoreleasepool {
for (int i = 0; i<1000; i++) {
Person *person3 = [[[Person alloc]init]autorelease];
}
_objc_autoreleasePoolPrint();
}
}
return 0;
}
打印如下,第一个 PAGE 多了一个 (full),代表第一个对象满了,没有了 (hot) 代表不是当前使用页,当前使用页切到下一个 PAGE 对象上了。
objc[10354]: ##############
objc[10354]: AUTORELEASE POOLS for thread 0x110cf5600
objc[10354]: 1004 releases pending.
objc[10354]: [0x7fd8fb009000] ................ PAGE (full) (cold)
objc[10354]: [0x7fd8fb009038] ################ POOL 0x7fd8fb009038
objc[10354]: [0x7fd8fb009040] 0x600000f1c020 Person
objc[10354]: [0x7fd8fb009048] 0x600000f1c030 Person
objc[10354]: [0x7fd8fb009050] ################ POOL 0x7fd8fb009050
objc[10354]: [0x7fd8fb009058] 0x600000f1c040 Person
......
......
......
objc[10354]: [0x7fd8fb00b000] ................ PAGE (hot)
......
......
......
objc[10354]: [0x7fd8fb00bfc8] 0x600000f1feb0 Person
objc[10354]: ##############
六、Runloop与Autorelease的关系(对象到底在什么时候调用对象的release方法)
先来看一段代码,MRC演示环境。
1、第1个 Observer 监听了 kCFRunLoopEntry 事件,会调用 objc_autoreleasePoolPush()
2、第2个 Observer 监听了 kCFRunLoopBeforeWaiting 事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush() ,监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), /// 1
kCFRunLoopBeforeTimers = (1UL << 1), /// 2
kCFRunLoopBeforeSources = (1UL << 2),/// 4
kCFRunLoopBeforeWaiting = (1UL << 5),/// 32
kCFRunLoopAfterWaiting = (1UL << 6),/// 64
kCFRunLoopExit = (1UL << 7),/// 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
以前打印当前RunLoop是可以看到注册的2个Observer的,两个 callout = _wrapRunLoopWithAutoreleasePoolHandler ,activities = 0x1 = 1 = kCFRunLoopEntry 和 activities = 0xa0 = 160 = kCFRunLoopBeforeWaiting | kCFRunLoopExit,但是现在打印看不到了,知道原因的大佬可以留言告诉我一下,谢谢!
结论:
- MRC时候对象调用 autorelease 方法加入到自动释放池后,release 的时机是在所在的一次 RunLoop 结束的时候释放。
-
ARC 时候由系统管理,系统没有调用 autorelease 方法,而是在合适的地方(离开对象作用域之前)底层调用了 person = nil 方法。
从汇编代码看确实相当于作用域离开前调用了 person = nil; 也就是 objc_storeStrong(&person,nil)
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
七、扩展 Tagged Pointer
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
有没有发现 obj->isTaggedPointer() 这里断言了,如果是Tagged Pointer类型对象就不加入到自动释放池里去,因为这种类型数据是存在指针里面的,指针本身也是有内存地址的,本身也能存储少量的数据。Tagged Pointer 详解
如有错误请帮忙指出,谢谢!转载请注明出处,喜欢的话,请点个赞吧!