iOS復(fù)習(xí)筆記:內(nèi)存管理之引用計(jì)數(shù)

現(xiàn)在我們使用Objective-C編寫iOS和Mac OS App的時(shí)候都是使用的是ARC來進(jìn)行內(nèi)存管理的。用一句話來總結(jié)ARC的功能的話,就是ARC使用編譯器來代替程序員做內(nèi)存管理的工作。雖然編譯器幫我們做了內(nèi)存管理工作,但是我們應(yīng)該弄清楚編譯器幫我們做了哪些工作,哪些工作是編譯器無法做的,還是需要我們程序員來完成的。

OC中對象的內(nèi)存管理是基于引用計(jì)數(shù)的內(nèi)存管理:(Reference Counted Memory Management)

我們可以想象一下我們在系統(tǒng)中使用對象的情形,在需要的時(shí)候向系統(tǒng)去申請內(nèi)存,不需要的時(shí)候釋放對象內(nèi)存,還給系統(tǒng)。如果長期霸占著不需要的內(nèi)存而不釋放,那就是刷流氓,會(huì)造成系統(tǒng)的內(nèi)存泄露。內(nèi)存泄露的多了,系統(tǒng)有可能會(huì)崩潰。

內(nèi)存管理 == 引用計(jì)數(shù),如果我們明白了引用計(jì)數(shù)的原理和使用的規(guī)則,OC的內(nèi)存管理就變的簡單了。

對象使用引用計(jì)數(shù)方法使得內(nèi)存管理變的很簡單。

我們需要弄清楚兩個(gè)問題:1. 引用計(jì)數(shù)的對象是什么?2. 引用計(jì)數(shù)是如何變化的?

引用計(jì)數(shù)的對象是什么:這個(gè)問題很簡單,在這里我們討論的引用計(jì)數(shù)的對象就是Objective-C對象。

引用計(jì)數(shù)是如何變化的:要想回答這個(gè)問題,我們需要先回答另一個(gè)問題,就是有哪些操作會(huì)影響引用計(jì)數(shù)的變化?

影響引用計(jì)數(shù)的變化的操作,大致有以下4種:

  1. 創(chuàng)建對象從而獲得對象的所有權(quán)(have ownership),引用計(jì)數(shù)+1, 從0變成1
  2. 持有對象從而取得對象的所有權(quán)(take ownership),引用計(jì)數(shù)+1
  3. 釋放對象從而放棄對象的所有權(quán)(relinguish ownership),引用計(jì)數(shù)-1;
  4. 如果一個(gè)對象的引用計(jì)數(shù)-1后為0,意味著這個(gè)對象不存在所有權(quán)關(guān)系,沒有被其它對象需要的可能,也就沒有存在的意義,系統(tǒng)會(huì)銷毀(destroy)對象回收對象分配的內(nèi)存。這個(gè)銷毀操作是系統(tǒng)自動(dòng)進(jìn)行的。

我們可以發(fā)現(xiàn)一個(gè)共同點(diǎn),上面的這些操作都是針對對象的所有權(quán)(ownership)進(jìn)行的,對象的所有權(quán)可以看作是操作對象的憑證或者授權(quán),沒有對象的所有權(quán)是不能對該對象進(jìn)行操作的。

所以要想操作一個(gè)對象,首先必要要獲得對象的所有權(quán),獲得所有權(quán)的方法有兩種:

  1. 如果這個(gè)對象已經(jīng)存在了,我們只需要聲明持有這個(gè)對象來獲得對象的所有權(quán),同時(shí)對象的引用計(jì)數(shù)會(huì)+1
  2. 如果對象不存在,我們需要新創(chuàng)建一個(gè)對象,同時(shí)自然會(huì)獲得這個(gè)新建對象的所有權(quán),新建對象的引用計(jì)數(shù)從0變成1。

對于一個(gè)我們沒有所有權(quán)的對象,是不能進(jìn)行操作的,如果不小心操作了一個(gè)沒有所有權(quán)的對象,程序有可能會(huì)崩潰。

OC中有相應(yīng)的方法來進(jìn)行對象所有權(quán)操作的方法:

對象所有權(quán)操作描述 OC方法
新建對象獲得對象的所有權(quán) alloc/new/copy/mutableCopy
取得已有對象的所有權(quán) retain
放棄對象的所有權(quán) release
銷毀對象(系統(tǒng)自動(dòng)調(diào)用) dealloc

內(nèi)存管理需要遵循的幾個(gè)規(guī)則:

  1. 能夠獲得任何新創(chuàng)建對象的所有權(quán):使用alloc/new/copy/mutableCopy開頭的方法來新建對象,并取得對象的所有權(quán)。我用四個(gè)字簡單總結(jié)就是你建你有。
  2. 能夠取得其它對象(不是自己創(chuàng)建的)的所有權(quán):可以使用retain方法取得對象的所有權(quán)來操作對象。對于不是以alloc/new/copy/mutableCopy開頭的方法返回得到的對象,雖然對象存在,但是我們沒有該對象的所有權(quán),不能對它進(jìn)行操作。只有通過retain方法取得對象的所有權(quán)后,才能進(jìn)行相應(yīng)的操作。簡單總結(jié)就是他建你有。
  3. 及時(shí)放棄不需要的對象的所有權(quán):當(dāng)一個(gè)對象不需要時(shí),不管是通過新建對象還是使用retain方法獲得的對象所有權(quán),都必須要調(diào)用release方法來放棄對該對象的所有權(quán)。簡單來說就是沒用快扔。
  4. 不能放棄你沒有所有權(quán)的對象:對于沒有所有權(quán)的對象,不能調(diào)用release方法,一旦調(diào)用了release方法,程序就有可能出錯(cuò)奔潰。簡單來說就是沒有別動(dòng)。

如果想要進(jìn)一步弄清楚OC對象的內(nèi)存管理規(guī)則,必須要清楚幾個(gè)問題和細(xì)節(jié):

  1. 引用一個(gè)對象和持有一個(gè)對象的區(qū)別?或者說獲得對象和獲得對象所有權(quán)的區(qū)別?
    面向?qū)ο蟮闹R告訴我們,一個(gè)對象的引用其實(shí)就是對象在內(nèi)存中的地址,通過引用我們可以找到我們需要的對象的位置。OC中的內(nèi)存管理告訴我們,持有一個(gè)對象是指我們不僅能夠知道對象在內(nèi)存中的位置,而且我們還擁有對象的所有權(quán),有了獲得了對象的所有權(quán)后,我們能夠操作這個(gè)對象。
    舉個(gè)不太恰當(dāng)?shù)睦樱罕热缯f你暗戀一個(gè)小姐姐,同時(shí)你也知道了小姐姐的住址,但是小姐姐并沒有同意和你在一起,此時(shí)你只是有了小姐姐的地址,然而并不能對小姐姐做什么;如果你不僅知道了小姐姐的住址,小姐姐也喜歡你同意和你在一起了,此時(shí)你獲得了小姐姐的所有權(quán)(不太恰當(dāng)?shù)恼f法),然后你就能對小姐姐進(jìn)行一番操作了。這就是引用一個(gè)對象和持有一個(gè)對象的區(qū)別。

下面使用一些代碼例子來說明內(nèi)存管理和引用計(jì)數(shù):

Example 1: 創(chuàng)建對象并獲得對象所有權(quán)

// 使用alloc方法,并獲得對象所有權(quán)
id obj1 = [[NSObject alloc] init];
// 使用new方法,并獲得對象所有權(quán)
id obj2 = [NSObject new];

Example 2: 不是自己創(chuàng)建的對象,獲得對象所有權(quán)

// 獲得一個(gè)不是自己創(chuàng)建的對象
id obj1 = [NSMutableArray array];
// 獲得對象的引用,但是沒有獲得對象的所有權(quán)
[obj1 retain];
//獲得了對象的所有權(quán)

Example 3: 不需要的對象,快快放棄對象的所有權(quán)

//創(chuàng)建對象并獲得對象所有權(quán)
id obj = [[NSObject alloc] init];
//獲得對象的所有權(quán),可以操作對象
...
  
[obj release];
// 放棄對象的所有權(quán),但是變量obj還是指向?qū)ο?,即對象的引用。但是不能訪問對象和操作對象。

Example 4: 方法返回對象時(shí)的所有權(quán)關(guān)系

//假設(shè)allocMyObject和myObject方法存在于MyClass中
- (id)allocMyObject {
  //新建對象并獲得所有權(quán)
  id obj = [[NSObject alloc] init];
  // 方法取得對象的所有權(quán)
  return obj;
  //返回對象,并轉(zhuǎn)把對象的所有權(quán)轉(zhuǎn)移到方法調(diào)用者
}

- (id)myObject {
  //新建對象并獲得所有權(quán)
  id obj = [[NSObject alloc] init];
  [obj autorelease];
  //方法放棄對象的所有權(quán)
  return obj;
  //返回對象,方法調(diào)用者無法取得對象的所有權(quán)
}

//myObj是MyClass的一個(gè)實(shí)例對象
//通過alloc開頭的方法創(chuàng)建的對象,獲得對象的所有權(quán)
id obj1 = [myObj allocMyObject];

//獲得對象的引用,但是沒有取得對象的所有權(quán)
id obj2 = [myObj myObject];

Example 5: 不能操作沒有所有權(quán)的對象

// 新建一個(gè)對象并取得對象的所有權(quán)
id obj1 = [[NSObject alloc] init];
//具有對象的所有權(quán)
[obj1 release];
//放棄對象的所有權(quán),此時(shí)不能操作對象


[obj1 release];
// 操作了一個(gè)沒有所有權(quán)的對象,系統(tǒng)可能崩潰

// 獲得一個(gè)對象的引用,但沒有取得對象的所有權(quán)
id obj2 = [myObj myObject];
// 此時(shí)沒有對象的所有權(quán)

[obj2 release];
// 操作了一個(gè)沒有所有權(quán)的對象,系統(tǒng)可能崩潰

總結(jié)

可以用一個(gè)狗和繩圈的比喻來描述對象引用計(jì)數(shù)的過程變化:狗脖子要有繩圈才能上街。當(dāng)需要帶狗上街時(shí),你就應(yīng)該給它套上一個(gè)繩圈,表明你取得了這個(gè)狗的所有權(quán)。當(dāng)別人看上了你的狗,他也可以給這個(gè)狗加上一個(gè)繩圈,表明也取得了這個(gè)狗的所有權(quán)。只要狗的脖子上面還有繩圈,說明這個(gè)狗是有人所有的,或者說是有需求的,這時(shí)候這個(gè)狗是跑不掉的。當(dāng)別人不需要這個(gè)狗時(shí),就會(huì)解開他擁有的繩圈,表明放棄了對這個(gè)狗的所有權(quán)。當(dāng)所有人都解開了繩圈,放棄對這個(gè)狗的所有權(quán)時(shí)。狗的脖子上是沒有繩圈的,這時(shí)狗子一看,沒有繩圈套著了,趕緊溜吧。這里的??就相當(dāng)于一個(gè)對象,脖子上的繩圈就是引用計(jì)數(shù)。

引用計(jì)數(shù)的需要遵循的規(guī)則:

  1. 你建你有
  2. 他建你有
  3. 沒用快扔
  4. 沒有別動(dòng)

我們還需要分清楚對象的引用和對象的所有權(quán)的區(qū)別,想想小姐姐的故事。

如果覺得有用可以去GitHub給個(gè)??。

參考:Pro Multithreading and Memory Management for iOS and OS X: With ARC, Grand Central Dispatch and Blocks.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容