我想大部分人都知道通常一個(gè)程序員會(huì)具有的美德。當(dāng)然了,有三種:懶惰,暴躁,傲慢。 ----Perl語言發(fā)明者Larry Wall
我想不管是iOS的,還是Java的初學(xué)者,內(nèi)存算得上心中的一個(gè)永遠(yuǎn)抹不去的痛吧,當(dāng)時(shí)作為初學(xué)者的我也是一度苦惱,不知道該如何理解這個(gè)內(nèi)存,隨著不斷的學(xué)習(xí),自己對內(nèi)存也有更深的了解.
內(nèi)存
說到內(nèi)存,不能不說一下內(nèi)存的分區(qū),內(nèi)存總共分為五大區(qū),分別是棧區(qū) 堆區(qū) 靜態(tài)區(qū) 常量區(qū) 代碼區(qū),五個(gè)區(qū)是按照內(nèi)存地址從大到小分配的.


其實(shí)我們最常用的還是棧區(qū)和堆區(qū)的內(nèi)存,然后首先來看一下棧區(qū)內(nèi)存的特點(diǎn)吧,
1.棧區(qū)內(nèi)存
1.局部變量的存儲(chǔ)空間基本都是棧區(qū),局部變量在函數(shù),循環(huán),分支中定義
2.在棧區(qū)的存儲(chǔ)空間由高向低分配,從低向高存儲(chǔ).!!
3.棧區(qū)內(nèi)存由系統(tǒng)負(fù)責(zé)分配和回收,程序員開發(fā)者沒有管理權(quán)限.
4.當(dāng)函數(shù),循環(huán),分支執(zhí)行結(jié)束后,局部變量的生命周期就結(jié)束了.之后不能再進(jìn)行使用,由系統(tǒng)銷毀
5.棧底,棧頂:棧底是棧區(qū)內(nèi)存的起始位置,先定義的變量所占用的內(nèi)存,從棧底開始分配,后定義的變量所占用的內(nèi)存,逐漸向棧頂分配.
6.入棧,出棧:入棧,定義新的局部變量,分配存儲(chǔ)空間.出棧,局部變量被銷毀,存儲(chǔ)空間被收回.
7.棧的特點(diǎn):先進(jìn)后出,后進(jìn)先出.例如:子彈夾添加子彈,打出子彈.
2.堆區(qū)內(nèi)存
1.由開發(fā)者負(fù)責(zé)分配和回收.
2.忘記回收會(huì)造成泄漏.
3.程序運(yùn)行結(jié)束后,需要及時(shí)回收堆內(nèi)存,但是如果不能及時(shí)回收堆內(nèi)存程序運(yùn)行期間可能會(huì)因?yàn)閮?nèi)存泄漏造成堆內(nèi)存被全部使用,導(dǎo)致程序無法使用.
3.常量區(qū)內(nèi)存
1.常量存儲(chǔ)在常量區(qū),例如:常量數(shù)字,常量字符串,常量字符,
2.常量區(qū)存儲(chǔ)空間由系統(tǒng)分配和回收
3.程序運(yùn)行結(jié)束后,常量區(qū)的存儲(chǔ)空間被回收
4.常量區(qū)的數(shù)據(jù)只能讀取,不能修改,修改的話會(huì)造成崩潰.
4.靜態(tài)區(qū)內(nèi)存
1.全局變量,使用static修飾的局部變量,都存儲(chǔ)在靜態(tài)區(qū).
2.靜態(tài)區(qū)的存儲(chǔ)空間由系統(tǒng)分配和回收.
3.程序運(yùn)行結(jié)束后,靜態(tài)區(qū)的存儲(chǔ)空間被回收,靜態(tài)變量的生命周期和程序一樣長.
4.靜態(tài)變量只能初始化一次,在編譯時(shí)進(jìn)行初始化,運(yùn)行時(shí)可以修改值
5.靜態(tài)變量如果沒有設(shè)置初始值,默認(rèn)為0.
5.代碼區(qū)內(nèi)存
1.由系統(tǒng)分配和回收
2.程序運(yùn)行結(jié)束之后,由系統(tǒng)回收分配過的內(nèi)存存儲(chǔ)空間
內(nèi)存管理
上面我們說到了內(nèi)存的五大區(qū),現(xiàn)在我從內(nèi)存的管理來說一下內(nèi)存,為什么我們開發(fā)應(yīng)用的時(shí)候要要注意內(nèi)存呢?由于移動(dòng)設(shè)備的內(nèi)存有限,所以每一個(gè)APP應(yīng)用程序的內(nèi)存也是有限的,App所占用的內(nèi)存較多時(shí),系統(tǒng)就會(huì)發(fā)出內(nèi)存警告.為了節(jié)省內(nèi)存的使用.所以我們就要使用到內(nèi)存管理,就是當(dāng)對象不再被使用的時(shí)候,我們要對其內(nèi)存及時(shí)的進(jìn)行回收.
內(nèi)存管理這一塊我要說的是我們在MRC(Manual Reference Counting)環(huán)境下需要手動(dòng)管理堆區(qū)的內(nèi)存,進(jìn)行release操作等,但是在 ARC(Automatic Reference Counting)環(huán)境下,我們不需要手動(dòng)管理我們的內(nèi)存.首先我們需要看一下如何切換ARC環(huán)境和MRC環(huán)境.
首選我們需要打開工程的配置面板,然后Build setting面板中搜索auto(自動(dòng))關(guān)鍵的字樣,然后就可以查找到ARC環(huán)境的切換選項(xiàng)了

下面我就說一下內(nèi)存管理的核心,內(nèi)存管理的核心就是引用計(jì)數(shù)的加減,引用計(jì)數(shù)相當(dāng)于對象的一個(gè)屬性,當(dāng)然了,這個(gè)屬性是不需要我們手動(dòng)創(chuàng)建的,系統(tǒng)會(huì)幫每一個(gè)對象進(jìn)行創(chuàng)建的.

引用計(jì)數(shù)器的作用:用來判斷對象是否應(yīng)該回收內(nèi)存空間,當(dāng)引用計(jì)數(shù)器為0時(shí),此時(shí)需要回收對象的內(nèi)存空間.
引用計(jì)數(shù)器的操作:
retain 引用計(jì)數(shù)器 +1
release 引用計(jì)數(shù)器 -1
retainCount 得到引用計(jì)數(shù)器的值
如果一個(gè)對象被釋放的時(shí)候,引用計(jì)數(shù)為0了,就會(huì)使用一個(gè)方法,析構(gòu)函數(shù)dealloc;
然后,我們就看一下內(nèi)存管理的黃金法則,
內(nèi)存黃金法則:
誰alloc,誰release!(包括new);
誰retain,誰release!
(retain) 引用計(jì)數(shù)+1
(release) 引用計(jì)數(shù)-1
誰copy,誰release!
野指針與內(nèi)存泄露
說到內(nèi)存管理就不得不說一下內(nèi)存泄露和野指針問題.
內(nèi)存泄漏
用動(dòng)態(tài)存儲(chǔ)分配函數(shù)動(dòng)態(tài)開辟的空間,在使用完畢后未釋放,結(jié)果導(dǎo)致一直占據(jù)該內(nèi)存單元,不能被任何程序再次使用,直到程序結(jié)束。即所謂內(nèi)存泄漏。
注意:內(nèi)存泄漏是指堆內(nèi)存的泄漏。
簡單的說就是申請了一塊內(nèi)存空間,使用完畢后沒有釋放掉。它的一般表現(xiàn)方式是程序運(yùn)行時(shí)間越長,占用內(nèi)存越多,最終用盡全部內(nèi)存,整個(gè)系統(tǒng)崩潰。由程序申請的一塊內(nèi)存,且沒有任何一個(gè)指針指向它,那么這塊內(nèi)存就泄露了。
野指針
“野指針”不是NULL指針,是未初始化或未清零的指針,他指向的內(nèi)存地址不是程序員想要的。人們一般不會(huì)錯(cuò)用NULL指針,因?yàn)橛胕f語句很容易判斷。但是“野指針”是很危險(xiǎn)的,if語句對它不起作用。野指針的成因主要有兩種:
一、指針變量沒有被初始化。任何指針變量剛被創(chuàng)建時(shí)不會(huì)自動(dòng)成為NULL指針,它的缺省值是隨機(jī)的,它會(huì)亂指一氣。所以,指針變量在創(chuàng)建的同時(shí)應(yīng)當(dāng)被初始化,要么將指針設(shè)置為NULL,要么讓它指向合法的內(nèi)存。
二、指針p被free或者delete之后,沒有置為NULL,讓人誤以為p是個(gè)合法的指針。別看free和delete的名字惡狠狠的(尤其是delete),它們只是把指針?biāo)傅膬?nèi)存給釋放掉,但并沒有把指針本身干掉。通常會(huì)用語句if (p != NULL)進(jìn)行防錯(cuò)處理。很遺憾,此時(shí)if語句起不到防錯(cuò)作用,因?yàn)榧幢鉷不是NULL指針,它也不指向合法的內(nèi)存塊。
#pragma mark---研究簡單的野指針---
Person *p = [Person new];
//使用KVC對屬性進(jìn)行賦值
[p setValue:@"棟棟" forKey:@"name"];
[p retain];
//調(diào)用方法
[p eat];
//釋放
[p release];
//此處是野指針,在正常的情況下是可以訪問的.如果想要查找野指針,就要打開僵尸模式,editscheme->diagnostics->zombie
[p eat];
[p release];//如果不release就會(huì)產(chǎn)生內(nèi)存泄漏.
上面說到了僵尸模式,然后我們就看看如何開啟僵尸模式,檢測野指針的存在.

拷貝
對于拷貝,也是讓大多數(shù)人頭疼的,因?yàn)榭截惪局局桶炎约航o弄糊涂了,所以,今天最后我還要說一下這個(gè)copy相關(guān)的問題,首先拷貝分為可變拷貝和不可變的拷貝.來看一下實(shí)際的范例吧
NSString * p1 =@"Jobs";
NSString *p2 = [NSString stringWithFormat:@"wang"];
NSString *p3 = [[NSString alloc]initWithString:p1];
NSString *p4 = [NSString stringWithString:p1];
NSLog(@"%@ ----- %p",p1,p1);
NSLog(@"%@ ----- %p",p2,p2);
NSLog(@"%@ ----- %p",p3,p3);
NSLog(@"%@ ----- %p",p4,p4);
NSLog(@"%p",p1);
然后打印結(jié)果是這樣的..

這樣我們不難發(fā)現(xiàn),p3和p4只是指針的重指向而已.
不可變拷貝
下面就是不可變拷貝,當(dāng)我們這樣使用的時(shí)候 程序是會(huì)crash掉的,雖然p2的類型是可變的字符串對象,由于p2是p1進(jìn)行不可變出來的,其實(shí)相當(dāng)于p2指向了p1,通過地址我們不難發(fā)現(xiàn)p1和p2指針指向的是相同的一個(gè)地址.
NSString * p1 =@"Jobs";
NSLog(@"%p",p1);
//可變字符串對象 指向 不可變拷貝的數(shù)據(jù)
NSMutableString * p2 = [p1 copy];
NSLog(@"%p",p2);
//如果上面使用的是 copy,那么此處就會(huì)crash,如果使用的是,mutableCopy那么將會(huì)改變
[p2 appendString:@"steve"];

</br>
可變拷貝
如果我們將上面的過程換成可變的拷貝,然后再進(jìn)行字符串的拼接會(huì)有什么情況的產(chǎn)生呢?
NSString * p1 =@"Jobs";
NSLog(@"%p",p1);
//可變字符串對象 指向 不可變拷貝的數(shù)據(jù)
NSMutableString * p2 = [p1 mutableCopy];
NSLog(@"%p",p2);
//如果上面使用的是 copy,那么此處就會(huì)crash,如果使用的是,mutableCopy那么將會(huì)改變
[p2 appendString:@"steve"];

通過上面的兩個(gè)實(shí)例,我們簡單的對可變拷貝和不可變拷貝做一下總結(jié),用于以后的工作中
上述總結(jié):
copy(不可變拷貝),如果使用了不可變拷貝,那么接收的對象不管是可變對象還是不可變的對象,都不能改變拷貝過來的內(nèi)容
mutableCopy(可變拷貝),如果使用了可變拷貝,那么接收對象不管是可變的還是不可變的對象,都可以改變拷貝過來的內(nèi)容
淺拷貝和深拷貝
對于淺拷貝和深拷貝,其實(shí)就是拷貝對象的區(qū)別,淺拷貝拷貝的是地址,而深拷貝拷貝的是對象的本身.
#pragma mark---怎么判斷深拷貝----
//通過兩個(gè)對象的地址來判斷,如果地址不同,那么就是深拷貝.
#pragma mark---怎么判斷淺拷貝----
//通過兩個(gè)對象的地址來判斷,如果地址相同,那么就是淺拷貝.
這里我給大家附加上一道面試題,來提高大家對copy的理解
#pragma mark--- 面試題 ---
//1.你怎么樣理解深拷貝和淺拷貝.
//淺拷貝:就如同人和影子,在內(nèi)存,人沒影子就沒了,影子沒了人就沒了.就是操作的同一個(gè)空間.
//深拷貝:就如同人和克隆,在內(nèi)存中,人沒了克隆還在,克隆沒有,人還在.就是操作的不同的空間.
//2.如何對深拷貝和淺拷貝進(jìn)行內(nèi)存釋放
//淺拷貝:釋放一個(gè)即可.因?yàn)獒尫拍膫€(gè)對象,都是同一個(gè)空間.
//深拷貝:釋放全部,因?yàn)槭莾蓚€(gè)對象,而且是兩個(gè)空間.
今天我就先說到這,后期將說一下,對象屬性的實(shí)現(xiàn)原理,如果您喜歡這篇文章就點(diǎn)個(gè) '喜歡' 吧,不要打賞,只求 ' 喜歡 ',謝謝各位看官了.
參考博客原文:http://blog.csdn.net/dangercheng/article/details/12618161