一、1、IOS開發(fā)中,內(nèi)存中的對象主要有兩類
一類是值類型,比如int、float、struct等基本數(shù)據(jù)類型。
一類是引用類型,即繼承自NSObject類的所有的OC對象。
A、 值類型會被放入棧中,
B、 引用類型會被放到堆中
@ 全局/靜態(tài)存儲區(qū),全局變量和靜態(tài)變量的存儲區(qū)域
@棧區(qū),在函數(shù)執(zhí)行過程中,函數(shù)內(nèi)局部變量的存儲單元可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束后這些存儲單元自動被釋放。
@堆區(qū),亦稱動態(tài)分配區(qū),由程序在運(yùn)行過程中動態(tài)申請分配和管理的區(qū),通常說的內(nèi)存管理,基本是指對于這一內(nèi)存區(qū)塊的管理
@常量區(qū),存儲程序運(yùn)行過程中用到的各種常量,不允許修改
二、Objective-C管理內(nèi)存的方式
每個對象(特指:類的實例)內(nèi)部都有一個retainCount的引用計數(shù),
使用alloc, new, copy或者mutableCopy等以及調(diào)用addObject等方法時,引用計數(shù)器+1,使用release時,引用計數(shù)器-1,當(dāng)引用計數(shù)器為0時,對象被釋放
- iOS內(nèi)存管理的法則(Swift不適用,Swift自動管理內(nèi)存)
? 誰創(chuàng)建誰釋放
? 誰retain誰釋放
2)MRC手動管理內(nèi)存(人工引用計數(shù))
當(dāng)引用計數(shù)為0的時候,必須回收,引用計數(shù)不為0,不能回收,如果引用計數(shù)為0,但是沒有回收,會造成內(nèi)存泄露。如果引用計數(shù)為0,繼續(xù)釋放,會造成野指針。為了避免出現(xiàn)野指針,我們在釋放的時候,會先讓指針=nil。
3)ARC自動管理內(nèi)存(自動引用計數(shù))
在ARC模式下,只要沒有強(qiáng)指針(強(qiáng)引用)指向?qū)ο?,對象就會被釋放。在ARC模式下,不允許使用retain、release、retainCount等方法。并且,如果使用dealloc方法時,不允許調(diào)用[superdealloc]方法。
ARC模式下的property變量修飾詞為strong、weak,相當(dāng)于MRC模式下的retain、assign。strong:代替retain,缺省關(guān)鍵詞,代表強(qiáng)引用。weak:代替assign,聲明了一個可以自動設(shè)置nil的弱引用,但是比assign多一個功能,指針指向的地址被釋放之后,指針本身也會自動被釋放。
@property的參數(shù)分為三類,也就是說參數(shù)最多可以有三個,中間用逗號分隔,每類參數(shù)可以從上表三類參數(shù)中人選一個。如果不進(jìn)行設(shè)置或者只設(shè)置其中一類參數(shù),程序會使用三類中的各個默認(rèn)參數(shù),默認(rèn)參數(shù):(atomic,readwrite,assign)
一般情況下如果在多線程開發(fā)中一個屬性可能會被兩個及兩個以上的線程同時訪問,此時可以考慮atomic屬性,否則建議使用nonatomic,不加鎖,效率較高;
retain,相當(dāng)于ARC中的strong
assign,相當(dāng)于ARC中的weak
屬性參數(shù)
ARC下基本數(shù)據(jù)類型默認(rèn)的屬性參數(shù)為(atomic,readwrite,assign),對象類型默認(rèn)的屬性參數(shù)為(atomic,readwrite,strong)
非ARC下基本數(shù)據(jù)類型默認(rèn)的屬性參數(shù)為(atomic,readwrite,assign),對象的默認(rèn)屬性參數(shù)為(atomic,readwrite,retain)
ARC下
assgin : 基本數(shù)據(jù)類型、枚舉、結(jié)構(gòu)體(非OC對象)
strong : 除NSString\block以外的OC對象
copy : NSString,數(shù)組,字典,block
weak : 當(dāng)2個對象相互引用,一端用strong,一端用weak。引用記數(shù)不會加1
非ARC下
assign,用于基本數(shù)據(jù)類型和C數(shù)據(jù)類型(int, float, double, char)另外還有id
retain,通常用于非字符串對象
copy,通常用于字符串對象、block、NSArray、NSDictionary
@@ strong與copy都是強(qiáng)類型,但這兩者有什么區(qū)別呢。什么情況下使用copy,什么時候使用strong呢?
copy是深復(fù)制,會指向新的對象,即創(chuàng)建新的內(nèi)存地址。不會跟著賦值的對象的值變化而變化,即不可變。 strong則是指向賦值對象所在的地址,所以當(dāng)賦值對象值發(fā)生變化時,也會跟著改變。
使用copy的概念應(yīng)該是出于安全的考慮。防止賦值給它的是可變的數(shù)據(jù)。
所以什么時候使用 strong,賦值對象改變時也要跟著改變。使用copy,賦值對象改變時property不跟著變化。
@@、retain、copy、assign的區(qū)別:
1.retain:當(dāng)對一個對象A調(diào)用retain,然后賦值給B時,對象的引用計數(shù)加1,A和B指向同一個內(nèi)存地址。
2.copy:當(dāng)對一個對象A調(diào)用retain,然后賦值給B時,對象的引用計數(shù)加1,而且生成了一個新的拷貝,A和B指向不一樣的內(nèi)存地址。
3.assign:當(dāng)對一個對象A調(diào)用retain,然后賦值給B時,對象的引用計數(shù)不變,A和B指向同一個地址。
四、那些內(nèi)存管理中的坑
1)循環(huán)引用
即A持有了B,B持有了A,導(dǎo)致無論是先釋放A還是B
解決這種親密關(guān)系導(dǎo)致的循環(huán)引用,采用弱引用即可
- Block中的坑
Block在訪問對象變量(即類對象)時有兩條隱含的retain對象規(guī)則,即:
A、如果訪問類屬性對象變量,則Block會強(qiáng)引用self,即retain一次類對象本身
B、如果訪問了局部對象變量,則Block會強(qiáng)引用局部變量自身一次
3)閉包中循環(huán)引用
閉包中變量的訪問范圍及持有變了隱含規(guī)則同Block
4)NSTimer中的對象retain問題
repeats參數(shù)被設(shè)置成YES時,target中的對象將永遠(yuǎn)不會被釋放,只有調(diào)用invalidate方法之后才會釋放target對象,從而釋放接收處理target對象。
- performSelector中的對象retain問題
只有當(dāng)執(zhí)行完成之后才會釋放target和argument對象,它的執(zhí)行前提條件是:1)時間到;2)滿足指定的Loop Modes。因此在發(fā)起該方法的類銷毀之前該方法不一定會被執(zhí)行,因此就會存在內(nèi)存泄漏的風(fēng)險。能否在dealloc或deinit中釋放呢?請看客考慮
五、自動釋放池
自動內(nèi)存釋放使用@autoreleasepool關(guān)鍵字聲明一個代碼塊,如果一個對象在初始化時調(diào)用了autorelase方法,那么當(dāng)代碼塊執(zhí)行完之后,在塊中調(diào)用過autorelease方法的對象都會自動調(diào)用一次release方法。這樣一來就起到了自動釋放的作用,同時對象的銷毀過程也得到了延遲(統(tǒng)一調(diào)用release方法)。對象的釋放延遲到自動釋放池銷毀的時候。不過這只是一種半自動的機(jī)制。
注意:
1)autorelease不會改變對象的引用記數(shù),如果Person對象引用記數(shù)非0,放入自動釋放池是無法被銷毀的。
2)自動釋放池實質(zhì)是當(dāng)自動釋放池銷毀后調(diào)用對象的release方法。
3)如果一個操作比較占用內(nèi)存(對象比較多或者對象占用資源比較多),最好不要放入自動釋放池或者考慮放入多個釋放池中。
4)ObjC中類庫中的靜態(tài)方法一般都不需要手動釋放,內(nèi)部已經(jīng)調(diào)用了autorelease方法.
1.自動釋放池實現(xiàn)了對象的延遲釋放,將釋放時機(jī)延后。當(dāng)對一個對象調(diào)用autorelease方法后,對象被加入自動釋放池。當(dāng)自動釋放池釋放時,會對自動釋放池中的對象調(diào)用release方法。
2.主線程會自動創(chuàng)建自動釋放池,自己創(chuàng)建的線程需要自己負(fù)責(zé)創(chuàng)建自動釋放池。在一個RunLoop周期開始時,系統(tǒng)會創(chuàng)建一個自動釋放池,當(dāng)RunLoop周期結(jié)束時,系統(tǒng)會釋放之前創(chuàng)建的自動釋放池。如果我們在使用autorelease時沒有自己創(chuàng)建自動釋放池,對象會在它所在的RunLoop周期結(jié)束時被釋放掉。一個UI事件,Timer調(diào)用,delegate調(diào)用,都會是一個新的Runloop。
3.類似于[NSString stringWithFormat:]這樣的類方法創(chuàng)建的對象默認(rèn)是使用了自動釋放池的,不需要釋放。
4.當(dāng)在短時間內(nèi)大量的使用自動釋放對象,要手動使用自動釋放池來釋放對象,否則內(nèi)存會在短時間內(nèi)瘋漲。
六、循環(huán)引用
- @class
對于循環(huán)依賴關(guān)系來說,比方A類引用B類,同時B類也引用A類
這種代碼編譯會報錯。當(dāng)使用@class在兩個類相互聲明,就不會出現(xiàn)編譯報錯
用法概括
使用@class 類名; 就可以引用一個類,說明一下它是一個類
和#import的區(qū)別(面試題)
l #import方式會包含被引用類的所有信息,包括被引用類的變量和方法;@class方式只是告訴編譯器在A.h文件中 B *b 只是類的聲明,具體這個類里有什么信息,這里不需要知道,等實現(xiàn)文件中真正要用到時,才會真正去查看B類中信息
l 如果有上百個頭文件都#import了同一個文件,或者這些文件依次被#improt,那么一旦最開始的頭文件稍有改動,后面引用到這個文件的所有類都需要重新編譯一遍,這樣的效率也是可想而知的,而相對來講,使用@class方式就不會出現(xiàn)這種問題了
l 在.m實現(xiàn)文件中,如果需要引用到被引用類的實體變量或者方法時,還需要使用#import方式引入被引用類
問題:如何解決循環(huán)引用的內(nèi)存管理。
解決方案:一端用assign,一端用retain
/*
1.@class的作用:僅僅告訴編譯器,某個名稱是一個類
2.開發(fā)中引用一個類的規(guī)范
1>在.h文件中用@class來聲明類
2>在.m文件中用#import來包含類的所有東西
- 循環(huán)retain
比如A對象retain了B對象,B對象retain了A對象
這樣會導(dǎo)致A對象和B對象永遠(yuǎn)無法釋放
七、didReceiveMemoryWarning、dealloc方法使用
1.didReceiveMemoryWarning方法:
首先調(diào)用[super didReceiveMemoryWarning]方法,然后檢查當(dāng)前視圖的父視圖是否為空,如果為空,則釋放掉一些不需要的數(shù)據(jù)。關(guān)于視圖界面的釋放不應(yīng)該在這個方法中,應(yīng)該放在viewDidUnload方法中。
3.dealloc方法:
對象釋放時調(diào)用,在這個方法中,要釋放掉所有的數(shù)據(jù)和輸出口。比如釋放輸出口:[xxx release];(不使用self關(guān)鍵字)
八、一些注意的地方
1.向集合(NSArray,NSDictionary等)添加對象時,被添加的對象會被執(zhí)行retain操作,當(dāng)從集合中移走對象或者集合對象被釋放時,集合中的對象會被執(zhí)行release操作。
2.要保證有多少個alloc、copy、multablecopy、retain消息,就要有多少個release或者autorelease,保證代碼平衡。
3.在程序中直接用@""創(chuàng)建的NSString對象,是常量,引用計數(shù)是-1,向它發(fā)送retain、release沒有效果。
4.在View中使用圖片時,大的圖片區(qū)域盡量使用小的圖片數(shù)據(jù)來填充,減小內(nèi)存占用。
5.[UIImage imageNamed:@""],次方法使用了系統(tǒng)緩存來緩存圖像,會長時間占用內(nèi)存,最好使用imageWithContentsOfFile方法。
6.數(shù)據(jù)要延遲加載,只在內(nèi)存中保留滿足需要的最少的數(shù)據(jù)和視圖元素,需要的時候再加載,不需要就馬上銷毀。
7.假如一個成員變量在property中使用了retain,當(dāng)使用self關(guān)鍵字對其賦值時,會對創(chuàng)建的對象再retain一次,造成內(nèi)存泄露。比如:self.xxx = [[XXXalloc] init];
對一個成員變量賦nil值時,self.xxx = nil,會調(diào)用xxx的release方法,并且將指針置空,xxx = nil,只是將指針置空。
9.在控制器中使用NSTimer會使當(dāng)前控制器引用計數(shù)加1,所以在控制器釋放之前,必須暫停和使定時器失效,否則控制器將不會被釋放。