iOS-ARC機制,iosarc原理
記憶體管理是開發軟體中重要的一個課題。如果記憶體管理不當,輕者記憶體泄露,重者程式崩潰。
下面重要講述一下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)dealloc11 {12 NSLog(@"%s",__FUNCTION__);13 }14 @endPeople.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。