深入理解ARC内存管理机制

作为一名IOS开发者,只会面向业务开发还不够,还必须要深入理解object-c的底层原理,只有这样才能事半功倍,写出健壮的代码。

我们知道在IOS5.0以前,我们开发者使用的是MRC内存管理技术,遵循“谁创建,谁管理,谁管理,谁释放”的原则,那时候我们乐此不疲,觉得已经很方面了。不过IOS5.0之后出现了ARC技术,我们程序员再也不用手动书写释放,保持这样的逻辑了。

当然,有很多程序员不太了解ARC技术,认为此技术会影响性能,其实大可不必担心,iOS的ARC和Android的GC机制很像,但是他们本身还是有很大的区别的。其中GCS是运行时特性,ARC是编译时特性。所以ARC对运行时性能没有任何影响,而且大大减少了开发者的编码时间,以及可能出现的错误概率。

ARC的使用很简单

ARC顾名思义就是自动引用计数,引申意我们可以理解为自动内存管理。自动内存管理难道我们真的不需要管理了吗?非也。ARC机制也不是万能的,也会导致内存泄露,对象循环引用的问题,我们在使用的时候,需要注意这些问题。

我们在MRC中,我们经常要使用release,autorelease,retain这些关键字,来保留引用计数或者释放对象。在ARC中,我们就不能这么处理了。因为编译器会根据我们的代码在合适的地方自动插入相关的retain和release等操作,相当于编译器帮我们做了这些事。

既然我们创建的对象,不用我们手动释放,系统在必要的时候会为我们释放,那么对象会在什么时候释放掉呢?

探讨这个问题之前,我们先说明一下使用ARC的基本准则。

strong引用指向的对象不会被释放。

一个对象没有强引用会立刻释放(或者理解为weak链断掉)。

弱引用指向的对象将要释放时自动为空。

注意:我们创建的对象默认是强引用,比如:animal = [Animal new];等价__strong Animal = [Animal new];

下面我们讲述的内容都围绕着这三个准则。

一、局部对象

首先创建一个工程,然后添加一个类People,下面为People的.m文件内容。

  1. @implementation People
  2. -(id)init
  3. {
  4.     if (self = [super init])
  5.     {
  6.         NSLog(@"%s",__FUNCTION__);
  7.     }
  8.     return self;
  9. }
  10. -(void)dealloc
  11. {
  12.     NSLog(@"%s",__FUNCTION__);
  13. }
  14. @end
  15. People.m
我们在viewDidLoad中添加People *p = [People new]; NSLog(@"%s",__FUNCTION__);
打印结果为:
2015-10-19 13:16:10.589 textarc[2627:93432] -[People init]
2015-10-19 13:16:10.600 textarc[2627:93432] -[ViewController viewDidLoad]
2015-10-19 13:16:10.600 textarc[2627:93432] -[People dealloc]
我们发现People这个对象别释放,从打印顺序上我们可以看到是在viewDidLoad执行完毕之后这个People对象被释放掉的。
局部对象为什么会在函数执行完毕之后被释放掉呢?
在viewDidLoad中,p是一个强引用,对象不会被释放,会打印NSLog中的内容。但是在函数执行完毕之后,强引用指针不在指向对象。根据上面的准则,没有强指针指向对象会被立刻释放,所以在执行完viewDidLoad之后,对象People会被释放掉。
如果我们在viewDidLoad中这样写:
__weak People *p = [People new];
NSLog(@"%s",__FUNCTION__);
我们可以猜测到打印结果就会跟上面的不同
2015-10-19 13:22:10.783 textarc[2682:96983] -[People init]
2015-10-19 13:22:10.783 textarc[2682:96983] -[People dealloc]
2015-10-19 13:22:10.784 textarc[2682:96983] -[ViewController viewDidLoad]
因为对象没有强指针引用,所以People会被释放,然后执行下面的打印。
二、全局变量
现在我们将People定义成全局对象,命名为_people。
我们在viewDidLoad中写下面代码:
 _people = [People new];
 NSLog(@"%s",__FUNCTION__);
打印结果为:
2015-10-19 13:28:32.076 textarc[2764:100790] -[People init]
2015-10-19 13:28:32.077 textarc[2764:100790] -[ViewController viewDidLoad]
我们发现并没有调用People的dealloc方法,因为在执行完viewDid这个函数时,还有一个强引用指针指向People,根据上面的准则,对象不会被释放。
如果我们在全局对象前面加上一个__weak:   __weak People *_people;
再执行上面的代码,打印结果为:
2015-10-19 13:31:47.816 textarc[2803:102843] -[People init]
2015-10-19 13:31:47.827 textarc[2803:102843] -[People dealloc]
2015-10-19 13:31:47.828 textarc[2803:102843] -[ViewController viewDidLoad]
出现这个结果不用解释了吧,虽然是一个全局的对象,但是是弱引用,没有强引用,对象会被释放掉。
三、全局和局部混合使用
现在我们定义一个全局行强指针People的对象p,同时创建一个局部性强指针对象p1.
viewDidLoad中为:
  1. People *p1 = [People new];
  2. p = p1;
执行结果为:
2015-10-19 13:49:32.380 textarc[3088:111876] -[People init]
2015-10-19 13:49:32.383 textarc[3088:111876] -[ViewController viewDidLoad]
因为p1是局部强引用,在函数执行完后按常理是被释放掉,但是在释放前,有一个全局性的强引用执行了它,所有People没有被释放掉。
如果我们在viewDid中这样写:
  1. __weak People *p1 = [People new];
  2. p = p1;
  3. NSLog(@"%s",__FUNCTION__);
打印结果为:
2015-10-19 13:50:43.647 textarc[3115:112734] -[People init]
2015-10-19 13:50:43.651 textarc[3115:112734] -[People dealloc]
2015-10-19 13:50:43.651 textarc[3115:112734] -[ViewController viewDidLoad]
因为局部对象是弱引用,对象会被释放掉,在赋值给全局强引用之前,它已经为空了,对象已经释放掉了。
四、创建对个对象嵌套
假设我们还有一个类,类名为Car,People有一个属性,@property(nonatomic,strong) Car *car;
现在我们把People声明一个全局对象p.
在viewDidLoad中:
  1. p = [People new];
  2. Car *c = [Car new];
  3. p.car = c;
  4. NSLog(@"%s",__FUNCTION__);
打印结果:
2015-10-19 13:57:21.883 textarc[3220:116572] -[People init]
2015-10-19 13:57:21.886 textarc[3220:116572] -[Car init]
2015-10-19 13:57:21.886 textarc[3220:116572] -[ViewController viewDidLoad]
Car虽然是局部引用,但是函数执行完毕后没有释放,因为p对其还有一个强引用,所有Car不会被释放掉。
如果我们将People释放掉,Car自然也会释放。
我们想要释放一个一个变量的话,我们可以直接将变量赋值为空,就会释放掉。
例如:
  1. p = [People new];
  2. Car *c = [Car new];
  3. p.car = c;
  4. p = nil;
  5. NSLog(@"%s",__FUNCTION__);
打印结果为:
2015-10-19 14:01:14.703 textarc[3265:118993] -[People init]
2015-10-19 14:01:14.706 textarc[3265:118993] -[Car init]
2015-10-19 14:01:14.706 textarc[3265:118993] -[People dealloc]
2015-10-19 14:01:14.706 textarc[3265:118993] -[ViewController viewDidLoad]
2015-10-19 14:01:14.707 textarc[3265:118993] -[Car dealloc]
五、循环引用问题
加入在People中有Car这个属性,在Car中有People这个属性。(注意交叉引用问题)
我们在viewDidLoad中:
  1. People * p = [People new];
  2. Car *c = [Car new];
  3. p.car = c;
  4. c.people = p;
  5. NSLog(@"%s",__FUNCTION__);
结果为:
2015-10-19 14:05:50.296 textarc[3320:121202] -[People init]
2015-10-19 14:05:50.298 textarc[3320:121202] -[Car init]
2015-10-19 14:05:50.298 textarc[3320:121202] -[ViewController viewDidLoad]
我们发现都没有释放,会导致内存泄露。当然我们可以将c,或者p赋值为nil将其释放掉。但是这样写很糟糕,我们需要判断什么释放决定释放。
如何解决这个问题呢?
在属性修饰的时候,不要同时使用strong,可以一个使用weak修饰,则运行结果如下:
2015-10-19 15:45:53.289 textarc[3937:150043] -[People init]
2015-10-19 15:45:53.298 textarc[3937:150043] -[Car init]
2015-10-19 15:45:53.298 textarc[3937:150043] -[ViewController viewDidLoad]
2015-10-19 15:45:53.298 textarc[3937:150043] -[People dealloc]
2015-10-19 15:45:53.298 textarc[3937:150043] -[Car dealloc]
 ARC总结
在使用ARC的时候,可以在一定程度上简化我们的编程操作,但是在使用的过程中也会出现内存泄露的问题,需要我们在实际使用过程中总结出现问题的情况,让我们的程序有更少的bug和潜在的bug。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: