iOS autorelease 实现原理及释放时机
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
person *p = [[person alloc] init];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
我们见文件转化为 C++ 文件,xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
,然后生成一个.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")));
person *p = ((person *(*)(id, SEL))(void *)objc_msgSend)((id)((person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("person"), sel_registerName("alloc")), sel_registerName("init"));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
这个是我们生成的 main 的源码,可以看到 在开头生成了一个 __AtAutoreleasePool __autoreleasepool;
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可以看到有构造函数和析构函数,当我们创建结构体的时候,会调用构造函数,当结构体销毁的时候就会调用析构函数。所以但我们调用 __AtAutoreleasePool __autoreleasepool;
d的时候,就会调用我们的构造函数,然后其实就是调用 objc_autoreleasePoolPush()
当我们除了作用于,结构体销毁的时候,就会调用析构函数,objc_autoreleasePoolPop(atautoreleasepoolobj);
就会调用这个函数,一个 push 一个 pop.
所以我们的代码就相当于
atautoreleasepoolobj = objc_autoreleasePoolPush();
person *p = [[person alloc] init];
objc_autoreleasePoolPop(atautoreleasepoolobj);
接下来结合源码分析,其实我们的 autoreleasepool ,底层是靠一个叫做AutoreleasePoolPage
的数据结构管理的
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
每个 autoreleasepoolpage 占用4096 个字节,除了用来存放他内部的成员变量,剩下的用来存放autorelease对象的地址,所有的 autoreleasepoolpage是通过双向链表连接起来的,child 会指向下一个autoreleasepoolpage,parent 指向上一个,每个 autoreleasepoolpage对象,里面存放着调用autorelease对象的地址值,存放着所有调用过的.
@autoreleasepool {
for (NSInteger i = 0 ; i < 1000; i ++) {
person *p = [[person alloc] init];
}
}
我们在 autorelpool里面调用了一千次,一个指针8个字节,一共8000个,我们的autoreleasepoolpage,在存储万自己的实例变量一些信息过后可能剩余4000个字节来存储对象的地址,那么就需要两个autoreleasepoolpage来存储。当我们调用pop的时候,就找到这1000个对象的地址值,然后释放
当我们调用 push 的时候,会将 POOL_BOUNDARY 压栈,其实内部就是定义值为0,然后返回当前地址,当我们有对象调用 autorelease之后,就会在他的下面去压栈,存储调用autorelease对象的地址了,当我们调用pop的时候,我们就会从后往前,挨个调用release,一直到我们穿进去的那个 POOL_BOUNDARY 的位置,这样就会将所有的autorelease兑现都释放掉。其中next指针指向的是第一个能存放autorelease对象地址的位置,当我们调用一次push的时候,就会插入一个POOL_BOUNDARY,每次 pop 的时候,遇到 POOL_BOUNDARY 就会结束,释放当前 autoreleasepoolpage 存储的所有对象,每次pop的时候,会传入POOL_BOUNDARY的地址,然后从最后一个入栈的autorelease对象开始释放,如果一个page页里面能存储下,那么POOL_BOUNDARY会一直在当前page页叠加,只有当前page存放不下,会新建一个page,然后入栈。
autorelease 什么时机会被释放
runloop 注册了两个监听,我们打印下 mainrunloop
<CFRunLoop 0x600003194300 [0x10db64c30]>{wakeup port = 0x2303, stopped = false, ignoreWakeUps = false,
3 : <CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
12 : <CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
"<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
"<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
"<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
"<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
2020-03-01 16:03:18.346330+0800 testautoreelease[34048:1997377] persom dealloc
可以看到,runloop 注册了监听,第一个observer监听了,KCFRunloopEntry事件,然后调用 objc_autoreleasePoolPush()
第二个observer 监听了,KCFRunloopBeforeWaiting 事件,然后调用objc_autoreleasePoolPop(),然后调用objc_autoreleasePoolPush()。
当我们runloop退出的时候,会调用一次 pop,和之前的 push 对上。
所以我们的 autorelease 是跟我们的runloop有关的,在我们这次runloop休眠之前会pop释放。
所以如果我们生成的对象是autorelease的形式,那么不会立刻释放,会等档次的runloop结束之后释放,但是我们发现我们在arc情况下,viewdidload 里面初始化一个,然后结束之后就会立刻释放,说明很有可能编译器为什么添加了release,当过了viewdidload作用于之后就会调用release将我们的对象释放。
网友评论