天天看點

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

一、記憶體管理

1、為什麼要進行記憶體管理

由于移動裝置的記憶體極其有限,是以每個APP所占的記憶體也是有限制的,當APP所占用的記憶體較多時,系統就會發送記憶體警告,每個APP可用的記憶體是被限制的,如果一個APP使用的記憶體超過20M,則系統會向該APP發送Momory Warning消息,收到此消息後,需要回收一些不需要再繼續使用的記憶體空間,比如回收一些不再使用的對象和變量等,否則程式會崩潰。

2、OC記憶體管理的範圍

管理範圍:

管理繼承NSObject的對象,對其他的基本資料類型無效。

分析原因:

本質原因是因為對象 和其他資料類型在系統中的存儲空間不一樣,其它局部變量主要存放于棧中,而對象存儲于堆中,當代碼結束時這個代碼塊中涉及的所有局部變量會被回收,指向對象的指針也被回收,此時對象已經沒有指針指向,但依然存在于記憶體中,造成記憶體洩露。

小結:

1>對象存儲于 堆中

2>其他局部變量主要存放于  棧中

存儲于棧中的局部變量的特性:當代碼塊結束時這個代碼塊中的所有局部變量會被 回收(指向對象的指針也被回收)

堆的存儲特性:堆的管理是要  程式員來管理,系統自己不會管理

各個記憶體區如下:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

2、記憶體管理的原理

1)對象的所有權及引用計數

1>對象所有權概念

如何對象 都可能擁有一個或多個所有者,如果一個對象至少還擁有一個所有者,它(對象)就會繼續存在

2>Cocoa所有權政策

任何自己建立的 對象 都歸自己所有,可以使用名字以“alloc”或“new”開頭或名字中包含“copy”的方法建立對象,可以使用retain來獲得一個對象的所有權

2)對象的引用計數器

每個OC對象都有自己的 引用計數器,是一個整數 表示對象被引用的次數,即現在有多少東西在使用這個 對象,對象剛被建立時,預設計數器值為1,當計數器的值變為0時,則對象銷毀。

在每個OC對象内部,都專門有 8個位元組 的存儲空間來存儲引用計數器

3)引用計數器的作用

引用計數器是判斷 對象 是否要回收的依據,就是當引用計數器從1變為0時,就回收(或引用計數器為0)。

注意:存在一種例外:對象為nil時,引用計數器為0,但不收回空間

4)對引用計數器的操作

給對象發送消息,進行相應的計數器操作

retain消息:使計數器+1,該方法傳回對象本身

release消息:使計數器-1,(并不代表釋放對象)

retainCount消息:獲得對象目前的引用計數器值(占位符為:%ld 或 %lu)

5)對象的銷毀

當一個對象的引用計數器為0時,那麼它将被銷毀,其占用的記憶體被系統收回。

當對象被銷毀時,系統會自動向對象發送一條dealloc消息,一般會重寫dealloc方法,在這裡釋放相關的資源,dealloc就像是對象的“臨終遺言”

一旦重寫了dealloc方法局必須調用[super  dealloc],并且放在代碼塊的最後調用(不能直接調用dealloc方法)。

一旦對象被回收了,那麼他所占據的存儲空間就不再可用,堅持使用會導緻程式崩潰(野指針錯誤)

注意:

1>如果對象的計數器不為0,那麼在整個程式運作過程,它占用的記憶體就不可能被回收(除非整個程式已經推出)

2>任何一個對象,剛生下來的時候,引用計數器都為1。(對象一旦建立号,預設引用計數器就是1)當使用alloc、new或者copy建立一個對象時,對象的引用計數器預設就是1

3、OC記憶體管理分類

Objective-C提供了三種記憶體管理方式:

MannulReference Counting(MRC  手動管理,在開發  iOS4.1 之前的版本的項目時我們要自己負責使用引用計數來管理記憶體,比如要手動 retain、release、autorelease等,而在其後的版本可以使用ARC,讓系統自己管理記憶體)

automatic  reference  counting(ARC  自動引用計數,iOS4.1之後推出的)

garbage collection (垃圾回收)。iOS不支援垃圾回收

ARC作為蘋果判斷提供的技術,蘋果推薦開發者使用ARC技術來管理記憶體:

開發中如何使用:需要了解MRC,但實際使用時盡量用ARC

二、手動記憶體管理

記憶體管理的關鍵如何判斷對象被回收?

重寫dealloc方法,代碼規範

(1)一定要[super dealloc],而且要放到最後

意義是:先釋放子類占用的空間,再釋放父類占用的空間

(2)對self(目前)所擁有的其他對象做一次release操作

例如:

-(void) dealloc

{

[_car  release];

[super dealloc];

}

注意:

永遠不要直接通過對象調用dealloc方法(實際上調用并不會出錯)

一旦對象被回收了,它占用的記憶體就不再可用,堅持使用會導緻程式崩潰(野指針錯誤)為了防止調用出錯,可以将“野指針”指向nil(0)。

下面我們通過執行個體程式的示範來體驗 retain、release、dealloc

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

總結:

1>對象剛建立時,擁有者有一個就是自己,retainCount=1;

2>對象每調用一次retain,擁有者+1retainCount-1

3>對象每調用一次release,擁有者-1retainCount-1

4>dealloc方法要重寫

5>當retainCount=0時,會自動調用 dealloc方法

2、記憶體管理的原則

1)原則:

隻要還有人在使用某個對象,那麼這個對象就不會被回收

隻要你想使用這個對象,那麼就應該讓這個對象的引用計數器+1

當你不想使用這個對象時,應該讓對象的引用計數器-1

2)誰建立對象,誰release(建立對象和release是成對出現)

(1)如果你通過alloc、new、copy來建立了一個對象,那麼你就必須調用 release或者autorelease方法

(2)不是你建立的就不用你去負責釋放

3)誰retain,誰release(retain和release是成對出現)

隻要 你調用了retain,無論這個對象是如何生成的,你都要調用release

4)總結

有始有終,有加就應該有減。曾經讓某個對象電腦加1,就應該讓其在最後-1

三、手動記憶體管理之——單個對象記憶體管理

1、單個對象的野指針問題

思考一個問題:

對象在堆區的空間已經釋放了,還能 再使用p1?

1)野指針錯誤:通路了一塊壞的記憶體(已經被回收的,不可用的記憶體)

2)僵屍對象:所占記憶體已經被回收的對象,僵屍對象不能再被使用。(預設情況下Xcode為了提高編碼效率,不會時時檢查僵屍對象,打開僵屍對象檢測)

注意:

1>空指針:沒有指向任何東西的指針,給空指針發送消息不會報錯

關于nil和Nil及NULL的差別

nil:A null pointer to an Objective-CObject.

nil 是一個對象值

Nil:A null pointer to an Objective-Cclass.

NULL:A null pointer to anything else.

NULL 是一個通用指針(泛型指針)

2>不能使用[釋放對象  retain]讓僵屍對象起死複生

3>野指針操作

下面通過執行個體程式來示範

Dog.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Dog.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

通過上面的列印可以得到以下總結:

1>遵守在手動記憶體管理中的原則:

> 誰建立,誰release

> 誰retain,誰release

下面我們再通過實驗來體驗一下是否可以使用 僵屍對象

Dog.h和Dog.m和上面一樣:

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

1》當程式運作到最後的一個[dog  release]時,存儲在堆區的對象dog([Dog new])的存儲空間已經釋放,但是存儲在棧區的指針dog、jd 并沒有釋放,是以用dog、jd來方法run方法、retain方法時會報錯,以為指針dog、jd指向的對象空間已經不在,這些操作是違法的是在使用僵屍對象

2》當在[dog release]以後,把指針dog、jd重新指派來指向nil,在調用run方法和retain時并不會報錯,這也是nil的一種用法,但是不推薦這樣使用。

注意:3》如果dog、jd重新指派指向nil是放在[dog release]以前,那就會造成記憶體的洩露!

2、避免使用僵屍對象的方法

因為如果程式較為複雜時,我們也不清楚執行哪一個release時,retainCount變為0,是以就會造成使用僵屍對象

下面我們通過執行個體程式示範看怎樣使用nil來避免使用僵屍對象

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

從程式可以看出在[person  release]後直接跟上一句person=nil後再繼續調用run方法、retain、時不會報錯。

3、對象的記憶體洩露

原因有:

1)沒有按照記憶體規則來做,而造成retain和release 個數不配配,導緻記憶體洩露

2)對象使用的過程中被指派了nil,導緻記憶體洩露

3)在函數或者方法中不當的使用retain或者release造成的問題

下面通過執行個體程式來體驗上面的記憶體洩露情況:

1)沒有按照記憶體規則來做,而造成retain和release 個數不配配,導緻記憶體洩露

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

因為retainCount不等于0,是以不會列印dealloc中的内容

2)對象使用的過程中被指派了nil,導緻記憶體洩露

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

本來就是使用nil不當,造成記憶體洩露

3)在函數或者方法中不當的使用retain或者release造成的問題

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

從main.m來看,程式是符合程式記憶體管理原則的,誰建立,誰release;最後一句

NSLog(@"\n2——%lu",[personretainCount]);本應該會因為調用僵屍對象而報錯,但是也沒有報錯。

四、手動記憶體管理之——多個對象記憶體管理

1、本節我們讨論的問題是:WR開着他的BMW汽車去兜風

分析這句話:有兩個類:人:WR、汽車:BMW

執行個體程式示範:

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

1>上面的程式就是我們在不考慮記憶體洩露問題時,我們一般會這樣寫程式,因為在前面我們就是這樣寫的

2>我們從列印輸出可以看出Person、Car的引用計數器是1,是以說明這樣寫程式造成了“記憶體洩露”

我們已經知道不能讓程式的記憶體洩露,是以下面我們将讨論如何管理“多個對象的記憶體管理”

執行個體程式示範如下:

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

從列印輸出可以看出:程式沒有造成記憶體洩露

但是該程式并不完善,是有缺陷的,比如改一下程式就會造成“記憶體洩露”

Car、Person類不變

main.m改變如下:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

上面的程式是脆弱的,當我們在[car  release]之後增加調用driver方法後,這裡程式就造成了記憶體洩露,因為WR沒有釋放,是以我們在WR釋放前調用該對象方法是合理的,隻能說該程式太“脆弱”

分析上面的問題:

上面程式的脆弱就是因為在調用driver方法的時候,Car已經銷毀了,是以:程式最好是在Person沒有銷毀之前Car一直存在,也就是Car的引用計數器CarRetainCount=1

下面通過執行個體程式來示範

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

1>在我們的main.m中還是遵守記憶體管理的原則:

誰建立,誰release

2>這次隻要Person沒有銷毀前可以任意的調用 driver方法,并且沒有造成記憶體洩露

五、set方法記憶體管理

1、set方法存在的問題

1)set方法寫法(一)

原對象無法釋放造成的記憶體洩露

2)set方法寫法(二)

原對象讷讷公狗釋放,但是引起新的問題,set自己的時候,造成野指針

3)set方法寫法(三)

判斷新傳入的對象是否是原來的對象,如果不是原來的對象則 釋放,然後再retain

下面通過執行個體程式來示範

1)set方法寫法(一)

原對象無法釋放造成的記憶體洩露

對于第一個set方法寫法的解決方法在上面程式中已經解決了

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

上面的程式也隻适合給人傳送一次車,但給人多次傳送時,必定造成記憶體洩漏

2)set方法寫法(二)

原對象能夠釋放,但是引起新的問題,set自己的時候,造成野指針

下面通過執行個體程式示範

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

1>從這個列印輸出來看,發現BMW的引用計數器等于1,直到最後也沒有變為0,是以造成了記憶體洩露

2>在main.m中依然遵守記憶體管理原則,誰建立,誰release

下面我們通過修改程式來改進

分析:當我們第二次調用 setCar方法時,讓前一個傳來的car銷毀,然後讓新傳來的car  retain就可以了

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

其實這個程式還是有些“脆弱”,當程式第二次傳入的car還是第一傳入的car時,就會報錯

下面通過執行個體示範來體驗一下:

Car、Person類中的程式和上面的一樣

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

1>從這個列印輸出來看,程式報錯了,而此程式和上一個程式例子的不同僅僅是因為本程式第二次調用的setCar方法是傳入的car和第一次傳入的car是一樣的。

2>分析報錯的原因

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

總結:因為我們二次調用setCar方法所傳入的car是相同的BMW

(1)當第二次調用setCar方法在[BMW release]前面,那麼不會有任何問題

原因:當我們建立BMW時BMWCount=1,

  當第一次調用setCar方法時

-(void)setCar:(Car *)car

{

        [_carrelease]; 此時的_car=nil,是以[_car release]

等于[nil release]

       _car=[carretain];此時_car=[BMW retain]

  BMWCount=2

}

當第二次調用setCar方法時

-(void)setCar:(Car *)car

{

        [_car release]; 此時的_car=BMW,是以[_car release]

等于[BMW release],BMWCount=1

        _car=[car retain];此時_car=[BMW retain]

 BMWCount=2

}

當執行[BMW release]後BMWCount=1

當執行[WR release]後BMWCount=0

是以不會造成記憶體洩露

(2)當第二次調用setCar方法在[BMW release]後面,就會發生報錯

原因:當我們建立BMW時BMWCount=1,

  當第一次調用setCar方法時

-(void)setCar:(Car *)car

{

        [_car release]; 此時的_car=nil,是以[_car release]

等于[nil release]

        _car=[car retain];此時_car=[BMW retain]

  BMWCount=2

}

當執行[BMW release]後BMWCount=1

當第二次調用setCar方法時

-(void)setCar:(Car *)car

{

        [_car release]; 此時的_car=BMW,是以[_car release]

等于[BMW release],BMWCount=0

        _car=[car retain];此時_car=[BMW retain]就是使用

僵屍對象,是以會  報錯    

}

下面我們通過分析來改程序式:

報錯原因就是因為我們二次傳入的對象都是同一個對象而造成的,假如程式檢查到是同一個對象時就不再去執行

[_carrelease];

_car=[carretain];

就不會造成使用僵屍對象的錯誤

下面我們通過執行個體程式來示範:

Car.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Car.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.h

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

Person.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

main.m

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理
記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

列印輸出:

記憶體管理、手動記憶體管理、單個對象記憶體管理、多個對象記憶體管理、set方法記憶體管理

小結:

從列印輸出來看Person、Car類都銷毀了,沒有造成記憶體洩露,

說明這樣的set寫法是沒有問題的

3、set方法的記憶體管理

1)基本資料類型格式:直接指派

int float doublelongstructenum

-(void)setAge:(int)age

{

    _age=age;

}

2)OC對象類型格式

-(void)setCar:(Car *)car

{

    //1.先判斷是不是新傳進來的對象

    //判斷新傳來的car是否和舊值(原來的)_car不同

   if(_car!=car)

    {

        //2.對舊對象做一次release

        //讓舊值(原來的)_car release釋放掉

        [_carrelease];//如沒有舊對象,則沒有影響

        //3、對新對象做一次retain

        //再讓新傳來來的car retain過後指派給舊值_car

       _car=[carretain];

    }

}

3)set方法的記憶體管理 原則

1、如果在一個類中,有其他類的對象(關聯關系)

2、set方法書寫的時候,要遵守:

判斷是否是同一個對象,release    舊值 , retain   新值

繼續閱讀