https://blog.sunnyxx.com/2014/10/15/behind-autorelease/
https://juejin.im/post/5a66e28c6fb9a01cbf387da1
https://www.jianshu.com/p/f87f40592023
文档里关于autorelease的描述
Decrements the receiver’s retain count at the end of the current autorelease pool block.
NSAutoreleasePool object contains objects that have received an autorelease message and when drained it sends a release message to each of those objects. Thus, sending autorelease instead of release to an object extends the lifetime of that object at least until the pool itself is drained
NSAutoreleasePool对象包含了已经收到autorelease消息的对象,当NSAutoreleasePool对象release时,它会给它包含的每一个对象发送release消息。因此没使用autoreleasee替代release相当于延长了对象的生命周期直到NSAutoreleasePool对象release为止。
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
在主线程里,当每一个runloop事件循环开始的时候,The Application Kit 会自动创建autorelease pool,事件循环结束时会释放这个autorelease pool。也就是说,在没有手加autorelease pool的情况下,autorelease对象是在当前的runloop迭代结束时释放的。
新建一个工程,在终端里cd到main.m所在的目录下,使用下面的命令:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
在目录下会生成一个main.cpp的文件。在这个文件的最下面可以看到:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
在文件中搜索__AtAutoreleasePool
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:
void * context = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(context)
上面两个函数的实现可以在runtime源码的NSObject.mm文件里找到:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。AutoreleasePoolPage是一个C++实现的类。
- AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
- 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
- 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
- AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
一个AutoreleasePoolPage对象内存如下图:
我们接下来看看 AutoreleasePoolPage 的 push 函数的作用和执行过程, 一个 push 操作其实就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_BOUNDARY ,并且返回插入的 POOL_BOUNDARY 的内存地址。这个地址在执行 pop 操作的时候作为函数的入参。
# define POOL_BOUNDARY nil
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
push 函数通过调用 autoreleaseFast 函数来执行具体的插入POOL_BOUNDARY操作。
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);
}
}
autoreleaseFast 函数在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:
- 当前 page 存在且没有满时,直接将对象添加到当前 page 中,即 next 指向的位置;
- 当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;
- 当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。
每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(void *context)作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
- 从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
通过Object.mm源文件,我们可以找到 -autorelease 方法的实现,它最终也是调用了AutoreleasePoolPage的autorelease函数:
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;
}
它跟 push 操作的实现非常相似。只不过 push 操作插入的是一个 POOL_BOUNDARY ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。
在MRC下:
使用alloc/new/copy/mutable名称开头的方法意味着自己生成并持有对象:
{
id obj = [[NSObject alloc] init];
}
使用alloc/new/copy/mutable以外的方法获取的对象,是非自己生成的对象:
{
//取得非自己生成的对象,但obj不持有对象
id obj = [NSArray array];
//obj持有对象
[obj retain]
}
ARC下,id类型和对象类型必须附加所有权修饰符:
- __strong
- __weak
- __unsafe_unretained
- __autoreleaseing
__strong是id类型和对象类型默认的所有权修饰符,在ARC下
{
id obj = [[NSObject alloc] init];
}
上面的代码和下面的代码是一样的
{
id __strong obj = [[NSObject alloc] init];
}
__strong修饰的变量在超出其变量作用域时,会释放其赋予的对象,上面的代码和下面的MRC下的代码是一样的:
{
id obj = [[NSObject alloc] init];
...
[obj release];
}
对于非自己生成的对象:
{
id __strong obj = [NSArray array];
}
在MRC下,上面的代码可记述如下:
{
id obj = [[NSArray array] retain];
...
[obj release];
}
ARC下不能使用autorelease和NSAutoreleasePool,使用__autoreleasing替代autorelease,使用@autoreleasepool{}替代和NSAutoreleasePool。
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
对象赋值给__autoreleaseing修饰的变量等价于在ARC无效时调用对象的autorelease方法。
如果使用非自己生成的方法获取对象:
@autoreleasepool{
id __strong obj = [NSArray array];
}
上面的[NSArray array]方法已经将生成的对象放入了自动释放池,因此obj不需要使用__autoreleasing将其放入自送释放池了。
+ (id)
{
id __strong obj = [NSArray new];
rerurn obj;
}
上面的代码里,return使得obj变量超出其作用域,所以obj持有的对象会自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool里。
id指针和对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
假设有一个方法的声明:
- (BOOL)perfomrOperationWithError:(NSError **)error
对象的指针默认修饰符是__autoreleasing,所以它等价于下面的代码:
- (BOOL)perfomrOperationWithError:(NSError *_autoreleasing*)error
把对象赋值给__weak修饰的变量时,会把对象的地址作为键值,把weak修饰的变量的地址作为值注册到weak表中,weak表作为散列表实现。当对象释放时,从weak表中获取以对象地址为键值的记录,通过记录的地址将
__weak修饰的变量置nil。