華山論劍之iOS內(nèi)存,內(nèi)存管理,copy(拷貝)

我想大部分人都知道通常一個(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)存地址從大到小分配的.
內(nèi)存的分區(qū)示意圖
各個(gè)分區(qū)的的特點(diǎn)
其實(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)了

ARC環(huán)境切換示意圖
下面我就說一下內(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ù)示意圖
引用計(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é)果是這樣的..


打印結(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"];
運(yùn)行的結(jié)果

</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"];
可變拷貝的打印結(jié)果

通過上面的兩個(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

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

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

  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,081評論 1 16
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 2,249評論 0 7
  • 多線程、特別是NSOperation 和 GCD 的內(nèi)部原理。運(yùn)行時(shí)機(jī)制的原理和運(yùn)用場景。SDWebImage的原...
    LZM輪回閱讀 2,120評論 0 12
  • __block和__weak修飾符的區(qū)別其實(shí)是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,594評論 0 6
  • iOS內(nèi)存管理 概述 什么是內(nèi)存管理 應(yīng)用程序內(nèi)存管理是在程序運(yùn)行時(shí)分配內(nèi)存(比如創(chuàng)建一個(gè)對象,會(huì)增加內(nèi)存占用)與...
    蚊香醬閱讀 5,815評論 8 119

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