在使用面向?qū)ο蟮木幊陶Z言進(jìn)行開發(fā)的過程中大都涉及到內(nèi)存管理相關(guān)的問題;JAVA、C#等語言采用GC(垃圾回收)機(jī)制來管理內(nèi)存的使用;而最早從事iOS開發(fā)的工程師則經(jīng)歷過MRC(手動(dòng)管理)內(nèi)存的階段,后期apple推出了ARC(自動(dòng)引用計(jì)數(shù))的方式來簡(jiǎn)化內(nèi)存的管理;那么ARC究竟是什么呢?ARC是如何進(jìn)行內(nèi)存管理的呢?
自動(dòng)引用計(jì)數(shù)(ARC)
- 創(chuàng)建一個(gè)對(duì)象就是在內(nèi)存中開辟了一塊空間來存儲(chǔ)對(duì)象的屬性和行為,對(duì)象都有自己的生命周期,系統(tǒng)如何判斷對(duì)象的生命周期完畢后就對(duì)它進(jìn)行回收呢?
- iOS系統(tǒng)采用的是引用計(jì)數(shù),在開辟的內(nèi)存區(qū)域中存在一個(gè)NSInteger類型的變量,對(duì)象一旦創(chuàng)建它的值就為1,(通常情況下)有強(qiáng)引用指向它的值就會(huì)+1,強(qiáng)引用置為nil,它的值就會(huì)-1(retain消息會(huì)使得引用計(jì)數(shù)+1,release消息會(huì)使得引用計(jì)數(shù)-1);在一次事件循環(huán)結(jié)束后如果對(duì)象的引用計(jì)數(shù)為0則系統(tǒng)就會(huì)回收該對(duì)象;那么一次的事件循環(huán)還發(fā)生了什么呢?
- 首先要介紹自動(dòng)釋放池(autoReleasePool):它的實(shí)質(zhì)是一個(gè)NSMutableArray,一次的事件循環(huán)都會(huì)創(chuàng)建一個(gè)自動(dòng)釋放池,事件循環(huán)中產(chǎn)生的對(duì)象會(huì)被依次加入到autoReleasePool中,事件循環(huán)結(jié)束后自動(dòng)釋放池會(huì)一次向存儲(chǔ)的對(duì)象發(fā)送release消息,使得對(duì)象的引用計(jì)數(shù)-1,當(dāng)此操作完畢后,引用計(jì)數(shù)為0的對(duì)象就會(huì)被系統(tǒng)回收了;
- 總的來說自動(dòng)引用計(jì)數(shù)(ARC)就是iOS系統(tǒng)用來進(jìn)行內(nèi)存管理手段,通過監(jiān)控對(duì)象的引用計(jì)數(shù)值來決定對(duì)象是否應(yīng)該回收;
判斷一個(gè)對(duì)象是否被回收的依據(jù)
對(duì)象的引用計(jì)數(shù)為0時(shí),Objective-C中會(huì)調(diào)用-(void)dealloc而Swift會(huì)調(diào)用deinit {};當(dāng)這兩個(gè)方法被正常調(diào)用時(shí)說明對(duì)象的內(nèi)存管理是正確的;但是也會(huì)出現(xiàn)對(duì)象不被正常釋放的情況,例如:兩個(gè)對(duì)象互相強(qiáng)引用造成循環(huán)引用,使用block或是閉包造成與self的循環(huán)引用等,那么該如何解決這種強(qiáng)引用循環(huán)呢?
循環(huán)強(qiáng)引用
類的實(shí)例之間的循環(huán)強(qiáng)引用
@interface Person()
@property(strong)Car* car;
@end
@interface Car()
@property(strong)Person* owner;
@end
上述代碼中創(chuàng)建Person和Car的實(shí)例后,為屬性賦值,就會(huì)造成兩者互為強(qiáng)引用,這樣就使得引用計(jì)數(shù)不能為0,ARC就無法對(duì)兩者進(jìn)行內(nèi)存的釋放; 那么該如何打破這種互相強(qiáng)引用呢?Objective-C和Swift都提供了weak關(guān)鍵字的機(jī)制來解決這個(gè)問題;使用weak修飾屬性在賦值的時(shí)候不會(huì)使引用計(jì)數(shù)+1,沒有了強(qiáng)引用那么對(duì)象就能正常釋放!除了weak在Swift中還提供了unowned(無主引用)解決強(qiáng)循環(huán)引用;
Swift中weak和unowned
當(dāng)兩個(gè)實(shí)例出現(xiàn)互相強(qiáng)引用:(1)實(shí)例的值為nil對(duì)邏輯上不造成影響,那么選擇weak(2)實(shí)例的值的一方必須存在,那么只能使用unowned;例如人和信用卡一樣,信用卡的擁有者必須要實(shí)際存在;
block和閉包中出現(xiàn)強(qiáng)循環(huán)引用
#import "HZBlock.h"
typedef void (^TestBlock)(NSString* message);
@interface HZBlock()
@property(nonatomic,copy)TestBlock testBlock;
@property(nonatomic,copy)NSString* name;
@end
@implementation HZBlock
-(void)testBlcok{
self.testBlock = ^(NSString* message){
// 循環(huán)引用
NSString* nameNew = self.name;
NSLog(@"%@",nameNew);
};
}
上述代碼中在block中使用self,編譯器會(huì)報(bào)警告,告知此處會(huì)出現(xiàn)循環(huán)引用(Capturing 'self' strongly in this block is likely to lead to a retain cycle);
在Objective-C中可以weak化self來解決此問題
// weak化self
__weak __typeof(self) weakSelf = self;
// 在block體中為了避免self被釋放,可以再次強(qiáng)引用
__typeof(&*weakSelf) strongSelf = weakSelf;
在Swift中定義捕獲列表解決閉包內(nèi)的引用循環(huán)
class AutoRefManager: NSObject {
let name: String
let text: String?
lazy var asHTML: (Void) -> String = {
// 捕獲列表
[unowned self] in
self.text!
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
}
總結(jié)
在開發(fā)中遇到類似的循環(huán)引用時(shí)要仔細(xì)思考,是否會(huì)造成內(nèi)存泄露的問題,然后再選擇合適的解決方案來解決出現(xiàn)的問題;