Retain特质属性在MRC中的陷阱以及ARC中对其的规避
现在的xcode建立的工程中默认使用arc,要想在你的程序中使用MRC需要在build phases->Compile Sources中找到你的文件在Complie Flags中加入-fno-objc-arc。
Retain属性的定义如下
@property(nonatomic,retain)NSString* retainPropertyString;
先看一段正常的程序代码
- (void)viewDidLoad {
[super viewDidLoad];
NSString* p1 =[[NSString alloc] initWithFormat:@"123"]; //p1 refcount = 1
self. retainPropertyString = p1; //p1 refcount = 2
[p1 release]; //release p1, refcount = 1
}
-(void)viewDidUnload{
self. retainPropertyString= nil;
/*[[NSString alloc] initWithFormat:@"123"] 这段内存块至此会被销毁*/
}
以上代码是一个正常的逻辑没有内存泄露也没有非法内存的存在。其中隐藏细节便是retainPropertyString属性设置方法的实现,编译器的默认实现如下
-(void)setRetainPropertyString:(NSString*)propertyString
{
[propertyString retain];
[_retainPropertyStringrelease];
_retainPropertyString =propertyString;
}
文字描述即使先保留新值,后释放旧值,最后更新实例变量令其指向新值。顺序很重要。加入还未保留新值就先把就只release,而两个值又指向同一块内存,并且那块内存在外没有被其他对象强引用,就会导致系统将那块内存永久释放。而再进行保留也无法令已经回收的内存复活。Strong强引用属性默认设置过程如同retain。
现在如果将以上viewDidLoad中代码改成如下
- (void)viewDidLoad {
[super viewDidLoad];
self. retainPropertyString = [[NSString alloc] initWithFormat:@"123"];
}
那么retainPropertyString所指向的内存块的引用计数为2,即使在viewDidUnload中设置self.retainPropertyString = nil,也无法避免存放“123”这块内存泄露了。并且在程序其他地方更有可能写出self.retainPropertyString = [[NSStringalloc] initWithFormat:@"abc"];
这样导致多处内存泄露。
如上所述,在mrc中如何规避这种内存泄露问题
1. 使用临时对象p1指向[[NSString alloc] initWithFormat:@"123"],在不需要的时候release p1. 就像一开始阐述的那样
2. self.retainPropertyString = [[NSString alloc] initWithFormat:@"123"];改写成
_ retainPropertyString = [[NSString alloc]initWithFormat:@"123"];
3. self.retainPropertyString = [NSSting stringWithFormat:@”123”];也可,这种情况自动释放池会避免内存泄露
4. 在dealloc方法中调用[retainPropertyString release];
以下我们来看一下ARC对上述出现的问题是如何做到规避的
我们创建两个NSObject的子对象来观察内存执行情况
// ViewController.m
#import"ViewController.h"
#import "TestObj.h"
@interfaceViewController ()
@end
@implementationViewController
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view,typically from a nib.
NSLog(@"begin test...");
TestObj* p = [[TestObj alloc] init];
}
@property(strong,nonatomic)TestObj1* mObj1;
//TestObj.m
#import "TestObj.h"
#import "TestObj1.h"
@implementation TestObj
-(id)init
{
if([super init]) {
self.mObj1 = [[TestObj1 alloc] init];
}
return self;
}
-(void)dealloc{
}
@end
// TestObj1.m
#import "TestObj1.h"
@implementation TestObj1
-(void)dealloc{
NSLog(@"mObj1 dealloc");
}
@end
以上代码输出结果如下:
2015-11-13 14:02:54.609 TestInvoked[50965:4982790] begintest...
2015-11-13 14:02:59.234 TestInvoked[50965:4982790] mObjdealloc
2015-11-13 14:02:59.235TestInvoked[50965:4982790] mObj1 dealloc
在TestObj.m文件中使用了self.mObj1 = [[TestObj1 alloc] init];这种方式在以前MRC中会导致内存泄露,在此我们发现TestObj1对象正常释放了。
再进一步看将TestObj.m中的init方法中写成
self.mObj1 = [[TestObj1 alloc] init];
self.mObj1 =[[TestObj1 alloc] init];
按照MRC中得情况会出现两次内存泄露,以下看ARC中得执行情况
2015-11-13 14:10:04.647TestInvoked[51397:4987676] begin test...
2015-11-13 14:10:09.000TestInvoked[51397:4987676] mObj1 dealloc
2015-11-13 14:10:09.000TestInvoked[51397:4987676] mObj dealloc
2015-11-13 14:10:09.000TestInvoked[51397:4987676] mObj1 dealloc
我们发现ARC帅呆了,TestObj1对象被释放两次,没有内存泄露的情况。
为什么TestObj和TestObj1对象都能正常释放,我们来一步步的分析一下
先看测试开始处
TestObj* p = [[TestObjalloc] init];
执行完这段代码,紧接着就是} ,编译器会为我们隐性的插入[p release];这样的代码,好这样TestObj对象自然不会内存泄露。
接下来我们在看TestObj.m中的init方法中
self.mObj1 = [[TestObj1 alloc] init]; //MRC中此处必然有泄漏
self.mObj1 =[[TestObj1 alloc] init]; //MRC中此处必然有泄漏
编译器处理过后的代码应该是如下这个样子
self.mObj1 = [[TestObj1 alloc] init];
[_mObj1 release];
self.mObj1 =[[TestObj1 alloc] init]; //此时之前分配的TestObj1对象就会被释放,此时mObj1指向了另外分配的内存
[_mObj1 release]
以上如有不实之处望各位同行指正