1.前言
随着Xcode的发展,我们已经不需要自己手动管理OC对象的引用计数了;编译器在编译的时候会根据环境上下文帮我们自动添加上管理的代码。但是我们需要通过MAC去了解内存管理中关于AutoRelease
的原理。
- 创建一个项目,我们把环境切换到MRC下,看看如果不自己手动管理内存对象会怎么样。如下图,新建一个项目,把环境切换到MRC下:
- 新建一个Person的类,添加如下的代码:
-------.h-----------
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
-------.m-----------
#import "Person.h"
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
- 使用如下(我这个是创建的MAC的项目):
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *pers = [[Person alloc] init];
pers.name = @"Test";
}
return 0;
}
- 运行后,可以看到,并没有打印Person的dealloc的函数。因为在main的函数中,并没有调用Person的release操作,现在我们加上release测试一下:
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *pers = [[Person alloc] init];
pers.name = @"Test";
[pers release];
}
return 0;
}
- 运行效果如下:
当调用了release的方法之后,Person是会被释放的。但是调用release有个不好的地方是:对象已经被释放,如果在release后面继续调用这个对象就会崩溃;这样在使用起来有比较多的局限性。因此,我们可以用autoRelease
来代替release
,替换后的效果如下:
- 当我们用
autoRelease
时,对象会在@autoreleasepool{}
这个大括号结束后,自动发送release
的操作。
2.AutoRelease的工作原理
2.1AutoRelease转成c++的代码
编写代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *pers = [[[Person alloc] init] autorelease];
}
return 0;
}
用命令行,进入该项目main
的文件夹,将main
的文件转成main.cpp
的文件,命令行的命令如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp
生成cpp后,查看代码:
也就是说,将
@autoreleasepool {
Person *pers = [[[Person alloc] init] autorelease];
}
这部分的代码转成了C++的代码如下:
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *pers = ((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"));
}
也就是说,@autoreleasepool {·····}
的的代码生成了
{
__AtAutoreleasePool __autoreleasepool;
·········
}
类似的代码。
2.2查看__AtAutoreleasePool
是个什么东西?
查看cpp里面是如何定义的:
解读如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() { //构建函数,在__AtAutoreleasePool被创建的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {//析构函数,在__AtAutoreleasePool被销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
也就是说,从调用@autoRelease{}
时,转成的c++的代码查看:
__AtAutoreleasePool
这个对象的作用域在 括号1 和 括号2 之间;- 当进入括号1后,创建
__autoreleasepool
这个变量; - 根据
struct __AtAutoreleasePool
这个结构体的定义可知,此时会调用构建函数atautoreleasepoolobj = objc_autoreleasePoolPush();
; - 当出了括号2后,
__autoreleasepool
这个对象就会被销毁( 因为__AtAutoreleasePool
这个对象的作用域在 括号1 和 括号2 之间),那么就会调用struct __AtAutoreleasePool
这个结构体的析构函数objc_autoreleasePoolPop(atautoreleasepoolobj)
;,最后等价于:
int main(int argc, const char * argv[]) {
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *pers = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
return 0;
}
如果有多个@autoRelase嵌套的话,如下图:
3. AutoreleasePoolPage
3.1AutoreleasePoolPage的结构
查看runtime的源码中关于objc_autoreleasePoolPush()的函数,如下:
在objc_autoreleasePoolPush函数中,会有一个AutoreleasePoolPage
的对象,其对象的结构如下:
其中,需要双向链表是因为,一个autoReleasePoolPage对象只有4040的空间存放调用autoRelease的对象,可能这个空间不够存放所有自动释放的对象,需要生成多个autoReleasePoolPage一起存放。
3.2AutoreleasePoolPage的工作原理
- 当编译器调用
objc_autoreleasePoolPush()
函数时,源码如下:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
而push()的源码如下:
也就是说,当调用push时会创建一个新的autoReleasePoolPage, 然后将POOL_BOUNDARY
存放到autoReleasePoolPage的如下位置:
然后其他的autoRelease的对象,再继续放到POOL_BOUNDARY
的后面(POOL_BOUNDARY
其实就是一个nil,空地址,用于做标记)。id next
是指向下一个autoRelease的对象
存放的地址。
- 当编译器调用
objc_autoreleasePoolPop()
函数时,会从最后一个autoRelease的对象
开始释放对象,直到POOL_BOUNDARY
。
3.3如果有多个@autoreleasepool嵌套的情况
代码如下:
那么poolPage的结构如下:
需要注意的是:p1 p2 p3 p4 应该在同一个poolPage里的,但是因为画图的时候,格数不够,就分开放了。
- 我们可以通过
extern void _objc_autoreleasePoolPrint(void);
的系统函数,来查看释放池里面的数据:
3.4插播一个小小的知识点:为什么我们可以调用系统的私有C语言函数?
我们可以在Person对象中,添加一个C语言的函数:
@implementation Person
void Person_test() {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
在main中,如果我们想要使用这个 Person_test() 函数,我们可以这么做:
extern void Person_test(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person_test();
}
return 0;
}
效果如下:
4.RunLoop与AutoRelease的关系
iOS在主线程中会注册两个observer:
- KCFRunLoopEntry (进入runLoop)
- KCFRunLoopBeforeWaiting | KCFRunLoopExit(runloop进入休眠或者退出)
runLoop与AutoRelease的关系
(1)当observer监听到KCFRunLoopEntry
时,就会objc_autoreleasePoolPush()
的操作;
(2)当observer监听到KCFRunLoopBeforeWaiting
时,就会调用objc_autoreleasePoolPop()
,把之前的自定释放对象释放掉; 并且调用objc_autoreleasePoolPush()
的操作,重新加入POOL_BOUNDRY
;
(3)当observer监听到KCFRunLoopExit
时,就会调用objc_autoreleasePoolPop()
函数。
总结
- 当给某个对象添加autoRelease时,如果是添加在
@autoReleasePool{}
里面的话,那么会出了**@autoReleasePool{}的右括号后**,直接释放; - 当给某个对象添加
autoRelease
时,是直接添加在某个方法上的,那么autoRelease释放的时机应该是runLoop休眠或者退出的时候
。 - ARC下,
不能调用release 和 autoRelease
,系统自动为我们生成的release操作,所以出了局部变量的作用域,对象就会比立即释放掉。