iOS-ARC機制


內存管理是開發軟件中重要的一個課題。如果內存管理不當,輕者內存泄露,重者程序崩潰。

下面重要講述一下iOS的ARC(Automatic Reference Counting))機制。

ARC的歷史由來

在iOS1.0的時候,蘋果公司沒有開放手機開發接口,不存在iOS開發者這個概念。

在iOS2.0的時候,我們管理內存使用的技術數MRC機制。

在iOS5.0的時候,蘋果公司推出了ARC機制。

我們知道在MRC管理內存的時候,我們需要遵守“誰創建,誰釋放,誰引用,誰管理”這個黃金法則,我們在創建對象的時候,需要考慮在什么時候釋放內存,有時候對內存管理機制不熟悉的話,就很有可能導致內存泄露和過度釋放的問題。

手動管理內存會導致很多問題的產生,在iOS5.0的時候,蘋果推出了自動內存管理ARC,當時使用的IDE是XCode4.0,在我們創建工程的時候,下面有一個選項,是否使用ARC,給開發者一個自主選擇,但是推出ARC,去很少人去使用ARC,在XCode5.0的時候,已經不存在這個選項,我們創建工程默認選擇的是ARC機制,我們我們想要使用MRC,需要手動配置工程。

在推出ARC的時候,很多人都說iOS的ARC和Android的GC機制很像,但是他們本身還是有很大的區別的。其中GCS是運行時特性,ARC是編譯時特性。

ARC的使用

ARC我們字面翻譯是自動引用計數,引申意我們可以理解為自動內存管理。自動內存管理難道我們真的不需要管理了嗎?非也。ARC機制也會導致內存泄露的問題,我們在使用的時候,需要注意這些問題。

我們在MRC中,我們經常要使用release,autorelease,retain這些關鍵字,來保留引用計數或者釋放對象。在ARC中,我們就不能這么處理了。如果我們使用這些關鍵字,我們的程序在基本的編譯都不能通過。

既然我們創建的對象,不用我們手動釋放,系統在必要的時候會為我們釋放,那么對象會在什么時候釋放掉呢?

探討這個問題之前,我們先說明一下使用ARC的基本准則。

  • 強引用指向的對象不會被釋放。
  • 一個對象沒有強引用會立刻釋放。
  • 弱引用指向的對象將要釋放時自動為空。

注意:我們創建的對象默認是強引用,比如:People = [People new];等價__strong People = [People 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
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中為:

People *p1 = [People new];
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中這樣寫:

__weak People *p1 = [People new];
p = p1;

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中:

p = [People new];
Car *c = [Car new];
p.car = c;
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自然也會釋放。

我們想要釋放一個一個變量的話,我們可以直接將變量賦值為空,就會釋放掉。

例如:

p = [People new];
Car *c = [Car new];
p.car = c;
p = nil;
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中:

People * p = [People new];
Car *c = [Car new];
p.car = c;
c.people = p;
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。
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM