前言
最近闲来无事,温习下ARC下内存管理方面的知识,在此做个学习笔记,若有不对之处,还望各位童鞋不吝指正。
方法命名与内存的关系
通常情况下,为了体现更好的代码规范,方法的命名尽可能地达到见名知意的效果。如果我们声明一个创建对象的方法,那么有四个关键字需要额外注意下,就是alloc、new、copy和mutableCopy。这里要分为两种情况进行讨论,暂且称之为方法命名的两种规则。
1. 若方法名字是以alloc、new、copy、mutableCopy这四个关键作为前缀,则方法返回的对象归调用者(caller)所有,也就是说caller要负责对创建的对象进行释放。
2. 若方法名字不是以上述四个关键字作为前缀,则方法返回的对象不归caller所有,也就是说caller不需要负责对创建的对象进行释放,对象会被加到AutoreleasePool中自动释放。如果caller不想让对象被自动释放,那么就需要对其进行强引用(ARC下默认为strong相当于执行一次retain操作),然后再在适当的时候执行release操作对其释放。
可能有的童鞋没有看明白是什么意思,不过没有关系,下面会通过具体的例子来进行阐述。如果到最后还是没有看明白,那么我可能会用三天时间来反思下我的文字表达能力[皱眉]~
解释与说明
1. 我们先来看第一种情况,即上述规则1。首先新建一个Person类,为了能够使用内存管理关键字来模拟ARC下编译器为我们设置的内存管理语义,故通过设置该编译单元(一个.m文件相当于一个编译单元)的Compiler Flags为-fno-objc-arc
来禁用ARC机制,同时覆盖release、dealloc等方法。然后我们再在ARC文件中创建一个person对象,代码如下:
Caller
// ARC
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (YES) { // if语句目的是让person对象的生命周期仅在当前作用域有效.
Person *_person = [Person newPerson]; // 这里只对`new`关键字开头的方法进行测试, 其他三种方法可自行验证.
NSLog(@"if语句执行中 %@", _person);
}
NSLog(@"if语句执行完");
}
@end
Callee
// MRC
@implementation Person
+ (Person *)newPerson {
Person *person = [[Person alloc] init];
return person;
}
- (instancetype)retain {
NSLog(@"%@", NSStringFromSelector(_cmd));
return [super retain];
}
- (oneway void)release {
NSLog(@"%@", NSStringFromSelector(_cmd));
[super release];
}
- (void)dealloc {
NSLog(@"%@", NSStringFromSelector(_cmd));
[super dealloc];
}
@end
运行程序,控制台输出如下:
2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] if语句执行中 <Person: 0x608000018010>
2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] release
2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] dealloc
2017-07-24 00:13:33.890 MemoryManagerUnderARC[5499:456138] if语句执行完
在Caller中,由于创建对象方法的名字是以new作为前缀,所以我们创建的对象归_person
所有 (_person
实际上是一个指针变量,OC中我们常称之为对象,具体请参考这里)。而_person
是个局部变量,所以作用域仅在if语句中有效,当if语句结束的时候,需要将其释放,如果不释放,那么在if语句之外就会因为无法访问该对象而引起内存泄露。�通过打印结果可看出,ARC下为我们自动执行release操作将对象释放。这里要注意一下,虽然ARC下变量的内存管理语义默认为strong,但是_person
并没有执行retain操作,因为这种情况下创建的对象已经归它所有,所以它无需再进行一次retain操作。如果我们此时将_person
的内存管理语义改成__weak或者是__unsafe_unretained,那么_person
就不会拥有这个对象,该对象创建完就会立即释放,当然,编译器也会给我们警告提示。
2. 我们再来看第二种情况,即上述规则2。我们对代码进行修改,如下:
Caller
// ARC
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (YES) { // if语句目的是让person对象的生命周期仅在当前作用域有效.
Person *_person = [Person createPerson]; // 除上述四个关键字之外,随便使用一个前缀或者不用前缀.
NSLog(@"if语句执行中 %@", _person);
}
NSLog(@"if语句执行完");
}
@end
Callee
// MRC
@implementation Person
+ (Person *)createPerson {
Person *person = [[Person alloc] init];
return [person autorelease]; //模拟ARC下代码,其实ARC下真正执行的并不是`autorelease`方法,而是`objc_autoreleaseReturnValue`,这里稍后会进行介绍。
}
- (instancetype)retain {
NSLog(@"%@", NSStringFromSelector(_cmd));
return [super retain];
}
- (oneway void)release {
NSLog(@"%@", NSStringFromSelector(_cmd));
[super release];
}
- (void)dealloc {
NSLog(@"%@", NSStringFromSelector(_cmd));
[super dealloc];
}
@end
再次运行程序,控制台输出结果如下:
2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] retain
2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] if语句执行中 <Person: 0x600000015550>
2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] release
2017-07-24 00:39:52.694 MemoryManagerUnderARC[5557:475107] if语句执行完
2017-07-24 00:39:52.697 MemoryManagerUnderARC[5557:475107] release
2017-07-24 00:39:52.697 MemoryManagerUnderARC[5557:475107] dealloc
如规则2所描述的那样,创建的对象会被加到AutoreleasePool当中,_person
无需负责该对象的释放操作。但是由于ARC下变量的内存管理语义默认为strong,所以_person
执行了一次retain操作,表示不想让对象自动释放,那么当if语句执行完的时候,即_person
作用域结束的时候,为了避免内存泄露,就必须得执行一次release
操作来抵消刚才的那次retain操作。至于上述打印结果最后执行的那次release操作是由于当前runloop进入休眠时AutoreleasePool进行pop操作将自动释放池里的对象进行释放,通过设置断点可查看:
![](https://img.haomeiwen.com/i1370044/31b20276291da3ad.png)
通过静态分析工具Analyze和Instruments对内存进行检测分析,没有发现内存问题,说明我们代码没毛病。看似完美无缺,但是细心的童鞋可能会有这样的疑问:既然
_person
不想让对象自动释放,那在创建对象的时候,为什么还要将对象添加到AutoreleasePool中?如果不加到AutoreleasePool中,那么_person
也就无需执行一次retain操作来持有它,而只需要在作用域结束的时候执行一次release操作将其释放即可。所以,ARC下为了免除
autorelease
和retain
这两步多余的操作来提高性能,使用函数objc_autoreleaseReturnValue
来替代autorelease
、函数objc_retainAutoreleasedReturnValue
来替代retain
。这两个函数runtime源码中有介绍。
(1) objc_autoreleaseReturnValue函数内部实现如下:
id objc_autoreleaseReturnValue(id obj)
{
#if SUPPORT_RETURN_AUTORELEASE
assert(tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY) == NULL);
if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
return obj;
}
#endif
return objc_autorelease(obj);
}
该函数会通过检测caller的retain指令(至于如何检测该指令,大神孙源博客黑幕背后的Autorelease中有介绍),来判断caller是否想持有创建的对象。若是的话,就不会执行autorelease
,而是将对象存储到TLS(线程局部存储)中。
(2)** objc_retainAutoreleasedReturnValue**函数内部实现如下:
id objc_retainAutoreleasedReturnValue(id obj)
{
#if SUPPORT_RETURN_AUTORELEASE
if (obj == tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY)) {
tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, 0);
return obj;
}
#endif
return objc_retain(obj);
}
该函数会先判断方法返回的对象和TLS中存储的是否一样,若一样,表示对象没有被加到AutoreleasePool中,则可以直接使用,否则就要执行一次retain操作。如此一来,就可以免除autorelease
和retain
这两步操作,从而提高了性能。
还是在这种情况下,如果_person
不想持有对象,我们将内存管理语义改为__unsafe_unretained,则对象就会因为AutoreleasePool的drain而自动释放。代码和打印结果如下:
if (YES) {
Person * __unsafe_unretained _person = [Person createPerson]; //ARC下这么写,相当于MRC下这么写Person * _person = [Person createPerson];
NSLog(@"if语句执行中 %@", _person);
}
NSLog(@"if语句执行完");
2017-07-24 01:46:31.583 MemoryManagerUnderARC[5648:526071] if语句执行中 <Person: 0x60000001efb0>
2017-07-24 01:46:31.583 MemoryManagerUnderARC[5648:526071] if语句执行完
2017-07-24 01:46:31.586 MemoryManagerUnderARC[5648:526071] release //自动释放
2017-07-24 01:46:37.888 MemoryManagerUnderARC[5648:526071] dealloc
至此,对文章开头所说的那两种规则阐述完毕。路漫漫其修远兮,希望正在看的你能为在下点个喜欢,作为鼓舞和激励,在此十分感谢!
参考资料
https://en.wikipedia.org/wiki/Thread-local_storage
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
https://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm
网友评论