iOS九面真經(jīng)0x02內(nèi)存管理

題型復(fù)現(xiàn)

iOS是怎么管理內(nèi)存的?
都有哪些與內(nèi)存管理有關(guān)的關(guān)鍵字?有何異同?
什么是ARC?MRC?區(qū)別?怎么進(jìn)行混編?
CF對(duì)象又該怎么辦呢?
Cocoa和CF框架間對(duì)象的轉(zhuǎn)換過程內(nèi)存所有權(quán)是怎么過渡的?

歷史沿革

任何一門語言都會(huì)創(chuàng)造變量,而這些變量時(shí)存儲(chǔ)在內(nèi)存空間的。計(jì)算機(jī)
(或移動(dòng)設(shè)備)的內(nèi)存是有限的,因此我們需要對(duì)內(nèi)存的使用做相應(yīng)的管理,在適當(dāng)?shù)臅r(shí)機(jī)分配內(nèi)存和釋放內(nèi)存。

在iOS5之前,iOS采用的是MRC(Manual Reference Counting),就是內(nèi)存的分配和釋放(或者說對(duì)象的生命周期)都是由我們程序猿手動(dòng)管理的。這就需要我們務(wù)必在,適當(dāng)?shù)臅r(shí)機(jī)去釋放已經(jīng)沒有用的對(duì)象來增加可用內(nèi)存空間。

上述對(duì)程序猿來說無疑是很麻煩的,稍有不慎就會(huì)造成一定的內(nèi)存問題。因此,Apple特地推出了ARC(Auto Reference Counting),就是對(duì)象的內(nèi)存分配和釋放不需要我們?nèi)プ隽恕4蟛糠智闆r下,我們只管創(chuàng)建就好,系統(tǒng)會(huì)為我們自動(dòng)地釋放已經(jīng)不需要的對(duì)象。

Reference Count

無論是MRC還是ARC,都有一個(gè)Reference Count的概念。那么,他是什么意思呢?

Reference Count,引用計(jì)數(shù),是每個(gè)對(duì)象都有的一個(gè)屬性。他代表當(dāng)前引用并持有這個(gè)對(duì)象的指針數(shù)量。只要他不為零說明當(dāng)前對(duì)象還有用,如果某一時(shí)刻他的值為零說明系統(tǒng)已經(jīng)不需要這個(gè)對(duì)象。這個(gè)時(shí)候就需要對(duì)這個(gè)對(duì)象釋放內(nèi)存空間。

貌似有點(diǎn)抽象哈,我們舉個(gè)簡單的??。

假設(shè)某組織成員名額是有限的,當(dāng)只要有組織內(nèi)的人支持你表示你有1票,只要有1票就可以出現(xiàn)在組織內(nèi)部。當(dāng)支持你的人多了1個(gè),你的票數(shù)就會(huì)相應(yīng)加1,反之亦然。當(dāng)你票數(shù)為0時(shí),你就必須離開這個(gè)組織,把名額讓給其他的人。

我們復(fù)盤這個(gè)例子,來看看他和內(nèi)存管理中引用計(jì)數(shù)的對(duì)應(yīng)關(guān)系:

  • 某組織:當(dāng)前程序系統(tǒng)
  • 成員名額:內(nèi)存空間
  • 成員:當(dāng)前程序的各個(gè)對(duì)象
  • 支持某成員的成員:引用(持有)某成員
  • 票數(shù):引用計(jì)數(shù)
  • 票數(shù)0->1:對(duì)象創(chuàng)建出現(xiàn)在程序中
  • 票數(shù)1->0:對(duì)象無用應(yīng)該得到釋放

MRC

首先來看MRC,從上面也能得知內(nèi)存管理的本質(zhì)就是引用計(jì)數(shù)的改變。在MRC中有以下接口可以引起引用計(jì)數(shù)的改變。

相關(guān)操作 相關(guān)API ReferenceCount變化
生成 alloc/new +1
持有 retain +1
拷貝 copy/mutableCopy +1
釋放 release -1
銷毀 dealloc == 0時(shí)執(zhí)行

MRC呢就是需要我們?cè)谶m當(dāng)?shù)臅r(shí)機(jī)去持有或釋放對(duì)象(執(zhí)行retain和release語句),典型的代表就是setter和getter方法的寫法。

//setter
-(void)setObject:(id) object {
    
    if (_object != object) {
        
        //前提是strong等表示強(qiáng)持有的對(duì)象類型
        //先釋放舊值
        [_object release];
        //再持有新值
        //非字符串對(duì)象
        [object retain];
        //字符串對(duì)象
        //[object copy]; //或者 [object mutableCopy];
        _object = object;
    }
}

//getter
- (void)getObject {
    
    //發(fā)生外部對(duì)象持有h該屬性對(duì)象
    //非字符串對(duì)象
    [_object retain];
    //字符串對(duì)象
    //[obj copy]; //或者 [obj mutableCopy];
    
    return _object;
}

ARC

ARC本質(zhì)上還是MRC,只不過之前"Manual"是程序猿自己在做,現(xiàn)在這項(xiàng)工作交給了編譯器。編譯器會(huì)根據(jù)語義分析,在適當(dāng)?shù)臅r(shí)機(jī)為程序加上retain和release等語句。

還是拿setter和getter方法作為??:

//setter
-(void)setObject:(id) object {
    
    if (_object != object) {
       
        _object = object;
    }
}

//getter
- (void)getObject {
    
    //發(fā)生外部對(duì)象持有h該屬性對(duì)象
    //非字符串對(duì)象
    [_object retain];
    //字符串對(duì)象
    //[obj copy]; //或者 [obj mutableCopy];
    
    return _object;
}

從我從事iOS開發(fā)以來,iOS就已經(jīng)全面過渡到了ARC時(shí)期。大部分OC對(duì)象的生命周期管理都不需要程序猿來做了,這對(duì)程序猿來說無疑是一件喜事。

屬性修飾符

我們?cè)诙x屬性時(shí)往往會(huì)有一個(gè)關(guān)鍵字來聲明該屬性與所屬對(duì)象的持有關(guān)系,而這種持有關(guān)系的保持又是靠所有權(quán)修飾符決定的。每一個(gè)屬性修飾福都有其對(duì)應(yīng)的所有權(quán)修飾符。

OC中主要涉及的修飾符及其對(duì)應(yīng)關(guān)系和作用如下:

屬性修飾符 對(duì)應(yīng)所有權(quán)修飾符 ReferenceCount變化
strong __strong +1
copy __strong +1
retain __strong +1
__unsafe_unretained __unsafe_unretained +0
assign __unsafe_unretained +0
weak __weak +0

__strong

__strong表示強(qiáng)引用。當(dāng)對(duì)__strong修飾的屬性進(jìn)行賦值某個(gè)對(duì)象時(shí),該對(duì)象引用計(jì)數(shù)會(huì)+1。對(duì)應(yīng)的屬性修飾符主要有三個(gè):

  • strong 對(duì)象類型默認(rèn)的屬性修飾符,如果沒有對(duì)應(yīng)關(guān)鍵字聲明,說明默認(rèn)使用他。

  • retain與strong關(guān)鍵字基本相同。唯一的區(qū)別是在MRC下對(duì)block的修飾,strong等同于copy(會(huì)形成__NSMallocBlock),retain則相當(dāng)于assign(會(huì)形成__NSStackBlock)。

  • copy與strong類似,主要用于子類擁有可變類型的不可變對(duì)象的修飾上,保護(hù)其類型安全性。其他還有類如block等特殊類型的修飾。

首先,我們來看retain和strong的區(qū)別。我們都知道一般都會(huì)使用copy修飾block(原理以及block分類暫且略過后面會(huì)有專門章節(jié)來分析),現(xiàn)在假設(shè)有這樣一段代碼:

@property(nonatomic, strong)void(^strongBlock)();
@property(nonatomic, retain)void(^retainBlock)();
//...

    NSInteger s = 520;
    _strongBlock = ^(void) {
        
        NSLog(@"i am strong block.I catch a int:%ld", s);
    };
    NSLog(@"_strongBlock Type:%@", [_strongBlock class]);
    
    _retainBlock = ^(void) {
        
        NSLog(@"i am retain block.I catch a int:%ld", s);
    };
    NSLog(@"_retainBlock Type:%@", [_retainBlock class]);

首先,我們?cè)贏RC環(huán)境下運(yùn)行。發(fā)現(xiàn)block類型相同,說明ARC下strong和retain修飾block是等效的,等同于copy。

ARC下

接著,我們?cè)僭贛RC下(不會(huì)切換MRC的請(qǐng)自行百度)運(yùn)行。發(fā)現(xiàn)retain修飾的block是棧類型的block,說明沒有進(jìn)行堆區(qū)拷貝(暫且知道后續(xù)會(huì)講原理)。

MRC下

接下來,我們?cè)倏纯碿opy的特殊之處。我們平時(shí)大部分時(shí)候使用它修飾NSString類型,但是我們有木有想過為什么不用strong呢?他們同樣是屬于__strong陣營。Talk is cheap, show you the code!

@property(nonatomic, strong)NSString *strongStr;
@property(nonatomic, copy)NSString *copyingStr;

NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"hello!!!"];
self.strongStr = mStr;
self.copyingStr = mStr;
[mStr appendString:@"HAHAHA"];
NSLog(@"_strongStr :%@", self.strongStr);
NSLog(@"_copyingStr :%@", self.copyingStr);

看下輸出結(jié)果,我們發(fā)現(xiàn)copy修飾的字符串并沒有隨之改變。而strong修飾的字符串竟然意外的隨mStr的改變而改變了,我們?cè)陂_發(fā)中并不希望我們的變量這樣突然秘密改變。

copy和strong修飾string

其實(shí),我們?cè)诖蛴∫幌滤麄兊牡刂肪蜁?huì)有新的發(fā)現(xiàn)。copy修飾的字符串與strong修飾的相比,和mStr的地址并不相同。這個(gè)有涉及到copy修飾符內(nèi)部會(huì)代用copy方法作深淺拷貝(暫作了解后續(xù)會(huì)深入分析)。

地址比較

綜上,為了保護(hù)NSString的數(shù)據(jù)安全性,我們需要使用copy修飾符而不是strong!這里可以進(jìn)行舉一反三的思考,我們不難發(fā)現(xiàn)NSMutableString是
NSString的子類,而且這種不安全發(fā)生在前者賦值給后者的時(shí)候。這就很容易聯(lián)想到NSArray,NSDictionary兩個(gè)類,其實(shí)他們也是需要用copy修飾的。關(guān)于他們的例子,讀者可以自己寫個(gè)Demo驗(yàn)證一下。

__unsafe_unretained

__unsafe_unretained表示弱引用,在屬性賦值的過程中并不影響賦值對(duì)象的引用計(jì)數(shù)。

  • __unsafe_unretained 修飾對(duì)象
  • assign 修飾諸如int,NSIngter,double等基本數(shù)據(jù)類型

__weak

__weak也表示弱引用,他是iOS5.0開始引入的一個(gè)所有權(quán)描述符。那么,既然有了__unsafe_unretained為什么還需要__weak呢?其實(shí)他是為了解決__unsafe_unretained有時(shí)會(huì)引起的crash問題的。__unsafe_unretained修飾的對(duì)象被釋放后指針并不置nil,這就使得再次訪問他就會(huì)發(fā)生crash。哈哈,是不很快猜到了__weak的特點(diǎn),沒錯(cuò)就是這一點(diǎn):

  • 修飾的對(duì)象被銷毀后,其指針會(huì)置nil

那么,同樣有了weak是不是就不需要__unsafe_unretained了呢?答案顯然是否定的。原因是除了對(duì)象類型還有基礎(chǔ)數(shù)據(jù)類型呢,assign屬于__unsafe_unretained陣營。

上面說到__weak比較消耗性能,那么他底層原理是怎么樣的呢?有興趣的可以參看我之前寫過的一篇筆記: 《直搗黃龍一探iOS weak實(shí)現(xiàn)原理》。了解其原理就會(huì)發(fā)現(xiàn),__weak還是比較消耗性能的,不過這一點(diǎn)與可能引起的crash相比不足為道。

__bridge

我們知道雖然有了ARC,但是他只對(duì)Cocoa框架的對(duì)象管用。我們往往在開發(fā)中還會(huì)用到CoreFoundation框架,這可是面向C的接口。因此這里還是需要我們用類似MRC那一套進(jìn)行手動(dòng)內(nèi)存管理。

  • CFReatin 同Cocoa中的retain
  • CFRelease 同Cocoa中的release

有的時(shí)候Cocoa里字符串類型為NSString,CoreFoundation為string類型。因此,在兩者協(xié)同開發(fā)時(shí)某個(gè)類型從一個(gè)框架進(jìn)入另一個(gè)框架需要做內(nèi)存所有權(quán)的交接。

  • __bridge 只做類型轉(zhuǎn)換,并不包括所有權(quán)的交接
  • __bridge_retained 類型轉(zhuǎn)換+所有權(quán)交接,Cocoa --> CoreFoundation
  • __bridge_transfer 類型轉(zhuǎn)換+所有權(quán)交接, CoreFoundation --> Cocoa

直接上代碼。

//cocoa 字符串
    NSString *cocoaString = @"I am cocoa string.";
    //轉(zhuǎn)換成CF類型需要做內(nèi)存所有權(quán)的轉(zhuǎn)移。這樣即使cocoaStr釋放,cfStr也不會(huì)受到影響
    CFStringRef cfStr = (__bridge_retained CFStringRef)cocoaString;
    //或者使用CFBridgingRetain函數(shù)
    CFStringRef cfStr1 = CFBridgingRetain(cocoaString);
    //使用cfStr
    //...
    //CF下使用完cfStr需要手動(dòng)釋放
    CFRelease(cfStr);
    
    //CF字符串
    CFStringRef cfString = CFSTR("I am a CF string.");
    //轉(zhuǎn)換成cocoa字符串要作所有權(quán)轉(zhuǎn)移。實(shí)際是對(duì)CF下的cfString做了一個(gè)release操作
    NSString *cocoaStr = (__bridge_transfer NSString *)cfString;
    //或者使用CFBridgingRelease函數(shù)
    CFStringRef cocoaStr1 = CFBridgingRelease(cfString);//CF下使用必有一個(gè)retain與之對(duì)應(yīng)

思考題

ARC給我們帶來了很大的遍歷。但這樣就一定不會(huì)出現(xiàn)內(nèi)存相關(guān)的問題嗎?

--------------------------------------------------------------2019夜

記錄的意義

不是所有的總結(jié)與記錄都有收獲
但毫無疑問
我們會(huì)在總結(jié)中慢慢成長
那些不經(jīng)意間的知識(shí)點(diǎn)將逐漸扎根
揮之不去
你的理解
亦會(huì)愈來愈深刻
這才是記錄的意義
-----------------------------非著名八線互聯(lián)網(wǎng)九流程序猿 chaors

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

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

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