iOS之從MRC到ARC內(nèi)存管理詳解

概述

在iOS中開發(fā)中泌绣,我們或多或少都聽說過內(nèi)存管理楷怒。iOS的內(nèi)存管理一般指的是OC對象的內(nèi)存管理集乔,因?yàn)镺C對象分配在堆內(nèi)存留荔,堆內(nèi)存需要程序員自己去動(dòng)態(tài)分配和回收;基礎(chǔ)數(shù)據(jù)類型(非OC對象)則分配在棧內(nèi)存中爹梁,超過作用域就會(huì)由系統(tǒng)檢測回收腹躁。如果我們在開發(fā)過程中纱皆,對內(nèi)存管理得不到位同眯,就有可能造成內(nèi)存泄露绽昼。

我們通常講的內(nèi)存管理,實(shí)際上從發(fā)展的角度來說须蜗,分為兩個(gè)階段:MRC和ARC硅确。MRC指的是手動(dòng)內(nèi)存管理,在開發(fā)過程中需要開發(fā)者手動(dòng)去編寫內(nèi)存管理的代碼明肮;ARC指的是自動(dòng)內(nèi)存管理菱农,在此內(nèi)存管理模式下由LLVM編譯器和OC運(yùn)行時(shí)庫生成相應(yīng)內(nèi)存管理的代碼。

通篇主要介紹關(guān)于內(nèi)存管理的原理及ARC和MRC環(huán)境下編寫代碼實(shí)現(xiàn)的差異晤愧。

提綱

一 引用計(jì)數(shù)
二 MRC操作對象的方法
  1. alloc/new/copy/mutableCopy
  2. retain
  3. release
  4. autorelease
  5. autorelease pool
三 ARC操作對象的修飾符
  1. __strong
    1.1 strong與變量
    1.2 strong與屬性
    1.3 strong的實(shí)現(xiàn)
  2. __weak
    2.1 weak和循環(huán)引用
    2.2 weak和變量
    2.3 weak的實(shí)現(xiàn)
    ? 2.3.1 weak和賦值
    ? 2.3.2 weak和訪問
  3. __unsafe_unretained
  4. _autoreleasing

具體介紹

一大莫、引用計(jì)數(shù)

在OC中蛉腌,使用引用計(jì)數(shù)來進(jìn)行內(nèi)存管理官份。每個(gè)對象都有一個(gè)與其相對應(yīng)的引用計(jì)數(shù)器只厘,當(dāng)持有一個(gè)對象,這個(gè)對象的引用計(jì)數(shù)就會(huì)遞增舅巷;當(dāng)這個(gè)對象的某個(gè)持有被釋放羔味,這個(gè)對象的引用計(jì)數(shù)就會(huì)遞減。當(dāng)這個(gè)對象的引用計(jì)數(shù)變?yōu)?钠右,那么這個(gè)對象就會(huì)被系統(tǒng)回收赋元。

當(dāng)一個(gè)對象使用完沒有釋放,此時(shí)其引用計(jì)數(shù)永遠(yuǎn)大于1飒房。該對象就會(huì)一直占用其分配在堆內(nèi)存的空間搁凸,就會(huì)導(dǎo)致內(nèi)存泄露。內(nèi)存泄露到一定程度有可能導(dǎo)致內(nèi)存溢出狠毯,進(jìn)而導(dǎo)致程序崩潰护糖。

二、MRC操作對象的方法

1.alloc/new/copy/mutableCopy

1.1 持有調(diào)用者自己的對象

在蘋果規(guī)定中嚼松,使用alloc/new/copy/mutableCopy創(chuàng)建返回的對象歸調(diào)用者所有嫡良,例如以下MRC代碼:

    NSMutableArray *array = [[NSMutableArray alloc] init];/*NSMutableArray類對象A*/
    
    NSLog(@"%p", array);
    
    [array release];//釋放

由于對象A由alloc生成,符合蘋果規(guī)定献酗,指針變量array指向并持有對象A寝受,引用計(jì)數(shù)器會(huì)加1。另外罕偎,array在使用完對象A后需要對其進(jìn)行釋放很澄。當(dāng)調(diào)用release后,釋放了其對對象A的引用颜及,計(jì)數(shù)器減1痴怨。對象A此時(shí)引用計(jì)數(shù)值為零,所以對象A被回收器予。不能訪問已經(jīng)被回收的對象浪藻,會(huì)發(fā)生崩潰。

1.2 持有非調(diào)用者擁有的對象

當(dāng)持有非調(diào)用者自己擁有的對象的時(shí)候乾翔,例如以下代碼:

    id obj = [Person person];
    
    [obj retain];
    
    /*do something*/
    
    [obj release];

此時(shí)obj變量獲得但不持有Person類對象爱葵,可以通過retain進(jìn)行持有該對象。當(dāng)我們使用完該對象反浓,應(yīng)該調(diào)用release方法釋放該對象萌丈。

注意:按照蘋果的命名規(guī)則,必須是alloc/new/copy/mutableCopy開頭雷则,并且是符合駝峰命名規(guī)則生成的對象才歸調(diào)用者所有辆雾。例如以下的方法,生成的對象不歸調(diào)用者所有:

- (id)newarray;
- (id)allocwithInfo;
- (id)coPySomething;
- (id)mutablecopyItem;

2.retain

2.1 retain和屬性

我們可以通過屬性來保存對象月劈,如果一個(gè)屬性為強(qiáng)引用度迂,我們就可以通過屬性的實(shí)例變量和存取方法來對某個(gè)對象進(jìn)行操作藤乙,例如某個(gè)屬性的setter方法如下:

- (void)setPerson:(Person *)person {

    [person retain];
    
    [_person release];
    
    _person = person;
    
}

我們通過retain新值,release舊值惭墓,再給實(shí)例變量更新值坛梁。需要注意的一點(diǎn)是:需要先retain新值,再release新值腊凶。因?yàn)槿绻屡f值是同一個(gè)對象的話划咐,先release就有可能導(dǎo)致該對象被系統(tǒng)回收,再去retain就沒有任何意義了钧萍。例如下面這個(gè)例子:

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (nonatomic, strong)Person *person;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    //實(shí)例變量持有Person類對象(P對象)褐缠。這樣賦值不會(huì)調(diào)用set方法
    _person = [[Person alloc] init];
    
    self.person = _person;//調(diào)用set方法
    
}

- (void)setPerson:(Person *)person {
    //release釋放對P對象的引用,P對象引用計(jì)數(shù)值變?yōu)榱惴缡荩瑒tP對象被系統(tǒng)回收
    [_person release];

    //由于P對象已經(jīng)被回收送丰,再去retain就容易出問題
    [person retain];
    
    _person = person;
    
}

@end

由于P對象被回收,對應(yīng)其所分配的內(nèi)存被置于“可用內(nèi)存池”中弛秋。如果該內(nèi)存未被覆寫器躏,那么P對象依然有效;如果內(nèi)存被覆寫蟹略,那么實(shí)例變量_person就會(huì)指向一個(gè)被覆寫的未知對象的指針登失,那么實(shí)例變量就變成一個(gè)“懸掛指針”。

2.2 retain和數(shù)組

如果我們把一個(gè)對象加入到一個(gè)數(shù)組中挖炬,那么該數(shù)組的addObject方法會(huì)對該對象調(diào)用retain方法揽浙。例如以下代碼:

    //person獲得并持有P對象,P對象引用計(jì)數(shù)為1
    Person *person = [[Person alloc] init];//Person類對象生成的P對象
    
    NSMutableArray *array = [NSMutableArray array];
    
    //person被加入到數(shù)組意敛,對象P引用計(jì)數(shù)值為2
    [array addObject:person];

此時(shí)馅巷,對象P被person和array兩個(gè)變量同時(shí)持有。

3.release

3.1 自己持有的對象自己釋放

當(dāng)我們持有一個(gè)對象草姻,如果在不需要繼續(xù)使用該對象钓猬,我們需要對其進(jìn)行釋放(release)。例如以下代碼:

    //array獲得并持有NSArray類對象
    NSArray *array = [[NSArray alloc] init];
    
    /*當(dāng)不再需要使用該對象時(shí)撩独,需要釋放*/
    [array release];
    
    //obj獲得但不持有該對象
    id obj = [NSArray array];

    //持有對象
    [obj retain];    

    /*當(dāng)不在需要使用該對象時(shí)敞曹,需要釋放*/
    [obj release];
3.2 非自己持有的對象不要釋放

當(dāng)我們不持有某個(gè)對象,卻對該對象進(jìn)行釋放综膀,應(yīng)用程序就會(huì)崩潰澳迫。

    //獲得并持有A對象
    Person *p = [[Person alloc] init];//Person類對象A
    
    //p釋放對象A的強(qiáng)引用,對象A所有者不存在
    //對象A引用計(jì)數(shù)為零剧劝,所以對象A被回收
    [p release];

    [p release];//釋放非自己持有的對象

另外橄登,我們也不能訪問某個(gè)已經(jīng)被釋放的對象,該對象所占的堆空間如果被覆寫就會(huì)發(fā)生崩潰的情況。

4.autorelease

autorelease指的是自動(dòng)釋放拢锹,當(dāng)一個(gè)對象收到autorelease的時(shí)候谣妻,該對象就會(huì)被注冊到當(dāng)前處于棧頂?shù)淖詣?dòng)釋放池(autorelease pool)。如果沒有主動(dòng)生成自動(dòng)釋放池面褐,則當(dāng)前自動(dòng)釋放池對應(yīng)的是主運(yùn)行循環(huán)的自動(dòng)釋放池。在當(dāng)前線程的RunLoop進(jìn)入休眠前取胎,就會(huì)對被注冊到該自動(dòng)釋放池的所有對象進(jìn)行一次release操作展哭。

autorelease和release的區(qū)別是:release是馬上釋放對某個(gè)對象的強(qiáng)引用;autorelease是延遲釋放某個(gè)對象的生命周期闻蛀。

autorelease通常運(yùn)用在當(dāng)調(diào)用某個(gè)方法需要返回對象的情況下匪傍,例如以下代碼:

    //外部調(diào)用
    Person *p = [Person person];
    NSLog(@"%p", p);//使用無須retain

    //持有則需要retain
    [p retain];
    _person = p;
    [_person release];

    //Person類內(nèi)部定義
+ (id)person {

    //創(chuàng)建的Person類對象由person獲得并持有
    Person *person = [[Person alloc] init];
   
    //[person release];

    [person autorelease];
    
    return person;
}

在外部調(diào)用,從方法名person知道觉痛,創(chuàng)建的對象由p指針變量獲得但不持有役衡。在函數(shù)內(nèi)部,person獲得并持有了Person類對象薪棒,所返回的person對象的引用計(jì)數(shù)加1手蝎。換句話說,調(diào)用者需要額外處理這多出來的一個(gè)持有操作俐芯。另外棵介,我們不能在函數(shù)內(nèi)部調(diào)用release,不然對象還沒返回就已經(jīng)被系統(tǒng)回收吧史。這時(shí)候使用autorelease就能很好地解決這個(gè)問題邮辽。

只要把要返回的對象調(diào)用autorelease方法,注冊到自動(dòng)釋放池就能延長person對象的生命周期贸营,使其在autorelease pool銷毀(drain)前依然能夠存活吨述。

另外,person對象在返回時(shí)調(diào)用了autorelease方法钞脂。該對象已經(jīng)在自動(dòng)釋放池中揣云,我們可以直接使用對象p,無須再通過[p retain]訪問;不過冰啃,如果要用實(shí)例變量持有該對象灵再,則需要對變量p進(jìn)行一次retain操作,示例變量使用完該對象需要釋放該對象亿笤。

5. autorelease pool

5.1 autorelease pool和RunLoop(運(yùn)行循環(huán))

每條線程都包含一個(gè)與其對應(yīng)的自動(dòng)釋放池翎迁,當(dāng)某條線程被終止的時(shí)候,對應(yīng)該線程的自動(dòng)釋放池會(huì)被銷毀净薛。同時(shí)汪榔,處于該自動(dòng)釋放池的對象將會(huì)進(jìn)行一次release操作。

當(dāng)應(yīng)用程序啟動(dòng),系統(tǒng)默認(rèn)會(huì)開啟一條線程痴腌,該線程就是“主線程”雌团。主線程也有一個(gè)與之對應(yīng)的自動(dòng)釋放池,例如我們常見的ARC下的main.h文件:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

該自動(dòng)釋放池用來釋放在主線程下注冊到該自動(dòng)釋放池的對象士聪。需要注意的是锦援,當(dāng)我們開啟一條子線程,并且在該線程開啟RunLoop的時(shí)候剥悟,需要為其增加一個(gè)autorelease pool灵寺,這樣有助于保證內(nèi)存的安全。

5.2 autorelease pool和降低內(nèi)存峰值

當(dāng)我們執(zhí)行一些復(fù)雜的操作区岗,特別是如果這些復(fù)雜的操作要被循環(huán)執(zhí)行略板,那么中間會(huì)免不了會(huì)產(chǎn)生一些臨時(shí)變量。當(dāng)被加到主線程自動(dòng)釋放池的對象越來越來多慈缔,卻沒有得到及時(shí)釋放叮称,就會(huì)導(dǎo)致內(nèi)存溢出。這個(gè)時(shí)候藐鹤,我們可以手動(dòng)添加自動(dòng)釋放池來解決這個(gè)問題瓤檐。如以下例子所示:

for (int i = 0; i < largeNumber; i++) {
        
        //創(chuàng)建自動(dòng)釋放池
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        
        //產(chǎn)生許多被注冊到自動(dòng)釋放池的臨時(shí)對象
        id obj = [Person personWithComplexOperation];
        
        //釋放池中對象
        [pool drain];
        
    }

如上述例子所示,我們執(zhí)行的循環(huán)次數(shù)是一個(gè)非常大的數(shù)字娱节。并且調(diào)用personWithComplexOperation方法的過程中會(huì)產(chǎn)生許多臨時(shí)對象距帅,所產(chǎn)生的臨時(shí)對象有可能會(huì)被注冊到自動(dòng)釋放池中。我們通過手動(dòng)生成一個(gè)自動(dòng)釋放池括堤,并且在每次循環(huán)結(jié)束前把該自動(dòng)釋放池的對象執(zhí)行release操作釋放掉碌秸,這樣就能有效地降低內(nèi)存的峰值了。

三 ARC操作對象的修飾符

1. __strong

1.1 __strong與變量

在ARC模式下悄窃,id類型和OC對象的所有權(quán)修飾符默認(rèn)是__strong讥电。當(dāng)一個(gè)變量通過__strong修飾符來修飾,當(dāng)該變量超出其所在作用域后轧抗,該變量就會(huì)被廢棄恩敌。同時(shí),賦值給該變量的對象也會(huì)被釋放横媚。例如:

{
    //變量p持有Person對象的強(qiáng)引用
    Person *p = [Person person];
    
    //__strong修飾符可以省略
    //Person __strong *p = [Person person];
}
    //變量p超出作用域纠炮,釋放對Person類對象的強(qiáng)引用
    //Person類對象持有者不存在,該對象被釋放

上述例子對應(yīng)的MRC代碼如下:

{
    Person *p = [Person person];
    
    [p retain];
    
    [p release];
}

可以看出灯蝴,ARC和MRC是對應(yīng)的恢口。只不過在ARC下,對象“持有”和“釋放”的內(nèi)存管理代碼交由系統(tǒng)去調(diào)用罷了穷躁。

1.2 strong與屬性

如果一個(gè)屬性的修飾符是strong,對于其實(shí)例變量所持有的對象耕肩,編譯器會(huì)在該實(shí)例變量所屬類的dealloc方法為其添加釋放對象的方法。在MRC下,我們的dealloc方法有如:

- (void)dealloc {

    [p release];//ARC無效
    
    [super dealloc];//ARC無效
}

在ARC下猿诸,我們就無須再去寫這樣的代碼了婚被。另外,在dealloc方法中梳虽,ARC只能幫我們處理OC對象址芯。如果實(shí)例變量持有類似CoreFoundation等非OC對象,則需要我們手動(dòng)回收:

- (void)dealloc {

    CFRelease(_cfObject);
}

在ARC下窜觉,dealloc方法一般用來執(zhí)行兩個(gè)任務(wù)谷炸。第一個(gè)就是手動(dòng)釋放非OC對象;第二個(gè)是接觸監(jiān)聽竖螃。另外淑廊,在ARC下我們不能主動(dòng)調(diào)用dealloc方法逗余。因?yàn)橐坏┱{(diào)用dealloc特咆,對象就不再有效。該方法運(yùn)行期系統(tǒng)會(huì)在合適的時(shí)機(jī)自動(dòng)去調(diào)用录粱。

1.3 strong的實(shí)現(xiàn)

在ARC中腻格,除了會(huì)自動(dòng)調(diào)用“保留”和“釋放”方法外,還進(jìn)行了優(yōu)化啥繁。例如某個(gè)對象執(zhí)行了多次“保留”和“釋放”菜职,那么ARC針對特殊情況有可能會(huì)將該對象的“保留”和“釋放”成對地移除。例如:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
    [tmp autorelease];//注冊到自動(dòng)釋放池(ARC無效)
    return tmp;
}

{
    //ARC
    _p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
    
    //實(shí)現(xiàn)展示
    Person *p = [Person person];//Person類對象引用計(jì)數(shù)為1
    _p = [p retain];//遞增為2
    [_p release];//遞減為1
}
    //清空自動(dòng)釋放池旗闽,Person類對象遞減為0酬核,釋放該對象

在上述代碼中,ARC對應(yīng)MRC的具體實(shí)現(xiàn)展示适室。在上面的展示中嫡意,+(id)person方法內(nèi)部會(huì)調(diào)用一次autorelease操作來延遲其返回對象的生命周期,并且稍后在自動(dòng)釋放池進(jìn)行release操作捣辆。_p通過retain來持有該對象蔬螟,使用完就執(zhí)行release操作。從而看出retain和autorelease是多余的汽畴,完全可以簡化成以下代碼:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
    return tmp;
}

{
    //ARC
    _p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
    
    //實(shí)現(xiàn)展示
    _p = [Person person];//Person類對象引用計(jì)數(shù)為1
    [_p release];//遞減為0旧巾,Person類對象被回收
}

那么ARC是如何判斷是否移除這種成對的操作呢?其實(shí)在ARC中忍些,并不是直接執(zhí)行retain和autorelease操作的鲁猩,而是通過以下兩個(gè)方法:

    objc_autoreleaseReturnValue(obj);//對應(yīng)autorelease
    objc_retainAutoreleasedReturnValue(obj);//對應(yīng)retain

以下為兩個(gè)方法對應(yīng)的偽代碼:

id objc_autoreleaseReturnValue(id obj) {

    if ("返回對象obj后面的那段代碼是否執(zhí)行retain") {
        //是
        set_flag(obj);//設(shè)置標(biāo)志位
        return obj;
    } else {
        return [obj autorelease];
    }
}

id objc_retainAutoreleasedReturnValue(obj) {

    if (get_flag(obj)) {
        //有標(biāo)志位
        return obj;
    } else {
        return [obj retain];
    }
}

通過以上兩段偽代碼,我們重新梳理一下代碼:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
    
    return objc_autoreleaseReturnValue(id tmp);
}

{
    //ARC
    _p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
    
    //實(shí)現(xiàn)展示
    Person *p = [Person person];//Person類對象引用計(jì)數(shù)為1
    _p = objc_retainAutoreleasedReturnValue(p);//遞增為1
    [_p release];//遞減為0
}

從上述示例代碼可以看出:

  1. 當(dāng)我們用變量p獲取person方法返回的對象前罢坝,person方法內(nèi)部會(huì)執(zhí)行objc_autoreleaseReturnValue方法绳匀。該方法會(huì)檢測返回對象之后即將執(zhí)行的那段代碼,如果那段代碼要向所返回的對象執(zhí)行retain方法,則為該對象設(shè)置一個(gè)全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的標(biāo)志位疾棵,并把對象直接返回戈钢。反之,在返回之前把該對象注冊到自動(dòng)釋放池是尔。

  2. 當(dāng)我們對Person類對象執(zhí)行retain操作的時(shí)候殉了,會(huì)執(zhí)行objc_retainAutoreleasedReturnValue方法。該方法會(huì)檢測對應(yīng)的對象是否已經(jīng)設(shè)置過標(biāo)志位拟枚,如果是薪铜,則直接把該對象返回;反之恩溅,會(huì)向該對象執(zhí)行一次retain操作再返回隔箍。

在ARC中,通過設(shè)置和檢測標(biāo)志位可以移除多余的成對(“保留”&“釋放”)操作脚乡,優(yōu)化程序的性能蜒滩。

2. __weak

2.1 weak和循環(huán)引用

__weak與我們上述所提到的__strong相對應(yīng),__strong對某個(gè)對象具有強(qiáng)引用奶稠。那么俯艰,__weak則指的是對某個(gè)對象具有弱引用。一般weak用來解決我們開發(fā)中遇到的循環(huán)引用問題锌订,例如以下代碼:

/*Man類*/
#import <Foundation/Foundation.h>

@class Woman;

@interface Man : NSObject

@property (nonatomic, strong)Woman *person;

@end

/*Woman類*/
#import <Foundation/Foundation.h>

@class Man;

@interface Woman : NSObject

@property (nonatomic, strong)Man *person;

@end

/*調(diào)用*/
- (void)viewDidLoad {
    [super viewDidLoad];

    Man *man = [[Man alloc] init];
    
    Woman *woman = [[Woman alloc] init];
    
    man.person = woman;
    woman.person = man;
}

從上述代碼可以看出竹握,Man類對象和Woman類對象分別有一個(gè)所有權(quán)修飾符為strong的person屬性。兩個(gè)類之間互相通過實(shí)例變量進(jìn)行強(qiáng)引用辆飘,Man類對象如果要釋放啦辐,則需要Woman類對象向其發(fā)送release消息。然而Woman類對象要執(zhí)行dealloc方法向Man類對象發(fā)送release消息的話蜈项,又需要Man類對象向其發(fā)送release消息芹关。雙方實(shí)例變量互相強(qiáng)引用類對象,所以造成循環(huán)引用战得,如下圖所示:

循環(huán)引用.png

這時(shí)候weak修飾符就派上用場了充边,因?yàn)閣eak只通過弱引用來引用某個(gè)對象,并不會(huì)真正意義上的持有該對象常侦。所以我們只需要把上述兩個(gè)類對象的其中一個(gè)屬性用weak來修飾浇冰,就可以解決循環(huán)引用的問題。例如我們把Woman類的person屬性用weak來修飾聋亡,分析代碼如下:

/*調(diào)用*/
- (void)viewDidLoad {
    [super viewDidLoad];

    Man *man = [[Man alloc] init];//Man對象引用計(jì)數(shù)為1
    
    Woman *woman = [[Woman alloc] init];//Woman對象引用計(jì)數(shù)為1
    
    man.person = woman;//強(qiáng)引用肘习,Woman對象引用計(jì)數(shù)為2

    woman.person = man;//弱引用,Man對象引用計(jì)數(shù)為1
}
    //變量man超出作用域坡倔,對Man類對象的強(qiáng)引用失效
    //Man類對象持有者不存在漂佩,Man類對象被回收
    //Man類對象被回收脖含, woman.person = nil;
    //Man調(diào)用dealloc方法,Woman類對象引用計(jì)數(shù)遞減為1
    //變量woman超出作用域投蝉,對Woman類對象強(qiáng)引用失效
    //Woman類對象持有者不存在养葵,Woman類對象被回收

從上述示例代碼可以看出,我們只需要把其中一個(gè)強(qiáng)引用修改為弱引用就可以打破循環(huán)引用瘩缆。類似的循環(huán)引用常見的有block关拒、NSTimer、delegate等庸娱,感興趣的自行查閱相關(guān)資料着绊,這里不作一一介紹。

另外熟尉,基于運(yùn)行時(shí)庫归露,如果變量或?qū)傩允褂脀eak來修飾,當(dāng)其所指向的對象被回收剧包,那么會(huì)自動(dòng)為該變量或?qū)傩再x值為nil棚放。這一操作可以有效地避免程序出現(xiàn)野指針操作而導(dǎo)致崩潰局骤,不過強(qiáng)烈不建議使用一個(gè)已經(jīng)被回收的對象的弱引用,這本身對于程序設(shè)計(jì)而言就是一個(gè)bug。

2.2 weak和變量

如果一個(gè)變量被__weak修飾,代表該變量對所指向的對象具有弱引用涵卵。例如以下代碼:

    Person __weak *weakPerson = nil;
    
    if (1) {
        
        Person *person = [[Person alloc] init];
        weakPerson = person;
        
        NSLog(@"%@", weakPerson);//weakPerson弱引用
    }
    
    NSLog(@"%@", weakPerson);

輸出結(jié)果如下:

[1198:73648] <Person: 0x600000018120>
[1198:73648] (null)

從上述輸出結(jié)果可以分析贴硫,當(dāng)超出作用域后挖诸,person變量對Person對象的強(qiáng)引用失效搂蜓。Person對象持有者不存在,所以該對象被回收丰涉。同時(shí)投慈,weakPerson變量對Person的弱引用失效猴抹,weakPerson變量被賦值為nil蝙砌。

另外需要注意的是阳堕,如果一個(gè)變量被weak修飾,那么這個(gè)變量不能持有對象示例择克,編譯器會(huì)發(fā)出警告恬总。例如以下代碼:

Person __weak *weakPerson = [[Person alloc] init];

因?yàn)閣eakPerson被__weak修飾,不能持有生成的Person類對象肚邢。所以Person類對象創(chuàng)建完立即被釋放壹堰,所以編譯器會(huì)給出相應(yīng)的警告:

Assigning retained object to weak variable; object will be released after assignment

2.3 weak的實(shí)現(xiàn)
2.3.1 weak和賦值

要解釋weak賦值的實(shí)現(xiàn),我們先看以下示例代碼:

    Person *person = [[Person alloc] init];
    
    Person __weak *p = person;

上述對應(yīng)的模擬代碼如下:

    Person *person = [[Person alloc] init];
    
    Person *p;
    objc_initWeak(&p, person);
    objc_destroyWeak(&p, 0);

上述兩個(gè)函數(shù)分別用來對變量p的初始化和釋放骡湖,它們都調(diào)用同一個(gè)函數(shù)贱纠。如下:

    id p;

    p = 0;

    objc_storeWeak(&p, person);//對應(yīng)objc_initWeak

    objc_storeWeak(&p, 0);//對應(yīng)objc_destroyWeak

根據(jù)上述代碼我們進(jìn)行分析:
1.初始化變量
當(dāng)我們使用變量弱引用指向一個(gè)對象時(shí),通過傳入變量的地址和賦值對象兩個(gè)參數(shù)來調(diào)用objc_storeWeak方法响蕴。該方法內(nèi)部會(huì)將對象的地址&person作為鍵值谆焊,把變量p的地址&p注冊到weak表中。

2.釋放變量
當(dāng)超出作用域浦夷,Person類對象被回收辖试,此時(shí)調(diào)用objc_storeWeak(&p, 0)把變量的地址從weak表中刪除。 變量地址從weak表刪除前劈狐,利用被回收對象的地址作為鍵值進(jìn)行檢索罐孝,把對應(yīng)的變量地址賦值為nil。

2.3.2 weak和訪問

當(dāng)我們訪問一個(gè)被__weak修飾過的變量所指向的對象時(shí)肥缔,其內(nèi)部是如何實(shí)現(xiàn)的莲兢?我們先看以下示例代碼:

    Person *person = [[Person alloc] init];

    Person __weak *p = person;
    
    NSLog(@"%@", p);

上述代碼對應(yīng)的模擬代碼如下:

    Person *person = [[Person alloc] init];
    
    Person *p;
    objc_initWeak(&p, person);
    id tmp = objc_loadWeakRetained(&p);
    objc_autorelease(tmp);
    NSLog(@"%@", tmp);
    objc_destroyWeak(&p);

通過上述代碼可以看出,訪問一個(gè)被__weak修飾的變量辫继,相對于賦值多了兩個(gè)步驟:

  1. objc_loadWeakRetained怒见,通過該函數(shù)取出對應(yīng)變量所引用的對象并retain;
  2. 把變量指向的對象注冊到自動(dòng)釋放池俗慈。

這樣一來姑宽,weak變量引用的對象就被加入到自動(dòng)釋放池。在自動(dòng)釋放池結(jié)束前都能安全使用該對象闺阱。需要注意的是炮车,如果大量訪問weak變量,就會(huì)導(dǎo)致weak變量所引用的對象被多次加入到自動(dòng)釋放池酣溃,從而導(dǎo)致影響性能瘦穆。如果需要大量訪問,我們通過__strong修飾的變量來解決赊豌,例如:

    Person __weak *p = obj;
    
    Person *tmp = p;//p引用的對象被注冊到自動(dòng)釋放池
    
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);

通過利用__strong中間變量的使用扛或,p引用的對象僅注冊1次到自動(dòng)釋放池中,有效地減少了自動(dòng)釋放池中的對象碘饼。

3. __unsafe_unretained

首先我們需要明確一點(diǎn)熙兔,如果一個(gè)變量被__unsafe_unretained修飾悲伶,那么該變量不屬于編輯器的內(nèi)存管理對象。該修飾符表明不保留值,即對其所指向的對象既不強(qiáng)引用住涉,也不弱引用麸锉。例如以下代碼:

__unsafe_unretained.png

根據(jù)上述代碼分析:當(dāng)超出作用域,變量p對Person類對象的強(qiáng)引用失效舆声。Person類對象的持有者不存在花沉,該對象被回收。當(dāng)我們再次使用該變量的時(shí)候媳握,因?yàn)槠渌硎镜膶ο笠呀?jīng)被回收碱屁,所以就會(huì)發(fā)生野指針崩潰。這一點(diǎn)區(qū)別于__weak蛾找,當(dāng)被__weak修飾變量所指向的對象被回收忽媒,該變量會(huì)賦值為nil。

另外腋粥,被__unsafe_unretained修飾的變量跟__weak一樣晦雨,不能持有對象實(shí)例。因?yàn)開_unsafe_unretained修飾的變量不會(huì)對生成的對象實(shí)例進(jìn)行保留操作隘冲,所以對象創(chuàng)建完立馬被回收闹瞧,編譯器會(huì)給出相應(yīng)的警告。

當(dāng)我們給被__unsafe_unretained修飾的變量賦值時(shí)展辞,必須保證賦值對象確實(shí)存在奥邮,不然程序就會(huì)發(fā)生崩潰。

4. __autoreleasing

在ARC和MRC中罗珍,autorelease作用一致洽腺,只是兩者的表現(xiàn)方式有所不同。
1.如果返回的對象歸對用者所有覆旱,如下:

    @autoreleasepool {
        
        Person __autoreleasing *p = [[Person alloc] init];
    }

上述代碼模擬代碼如下:

    id pool = objc_autoreleasePoolPush();
    Person *p = [[Person alloc] init];//持有
    objc_autorelease(p);
    objc_autoreleasePoolPop(pool);

2.如果返回的對象不歸調(diào)用者所有蘸朋,如下:

    @autoreleasepool {
        
        Person __autoreleasing *p = [Person person];
    }

上述代碼模擬代碼如下:

    id pool = objc_autoreleasePoolPush();
    Person *p = [Person person];
    objc_retainAutoreleasedReturnValue(p);//持有(優(yōu)化后的retain方法)
    objc_autorelease(p);
    objc_autoreleasePoolPop(pool);

從上面兩個(gè)例子可以看出,__autoreleasing的實(shí)現(xiàn)都是通過objc_autorelease()函數(shù),只不過是持有方法有所不同而已扣唱。

至此藕坯,內(nèi)存管理就介紹完畢了。由于技術(shù)有限噪沙,難免會(huì)存在紕漏炼彪,歡迎指正。轉(zhuǎn)載請注明出處正歼,萬分感謝辐马。

參考資料:
Objective-C 高級編程 iOS和OS X多線程和內(nèi)存管理
Effective Objective 2.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市局义,隨后出現(xiàn)的幾起案子喜爷,更是在濱河造成了極大的恐慌膜楷,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞奋,死亡現(xiàn)場離奇詭異赌厅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)轿塔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門特愿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勾缭,你說我怎么就攤上這事揍障。” “怎么了俩由?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵毒嫡,是天一觀的道長。 經(jīng)常有香客問我幻梯,道長兜畸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任碘梢,我火速辦了婚禮咬摇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煞躬。我一直安慰自己肛鹏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布恩沛。 她就那樣靜靜地躺著在扰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雷客。 梳的紋絲不亂的頭發(fā)上芒珠,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音佛纫,去河邊找鬼妓局。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呈宇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播局雄,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼甥啄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炬搭?” 一聲冷哼從身側(cè)響起蜈漓,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤穆桂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后融虽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體享完,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年有额,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了般又。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巍佑,死狀恐怖茴迁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萤衰,我是刑警寧澤堕义,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站脆栋,受9級特大地震影響倦卖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椿争,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一糖耸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丘薛,春花似錦嘉竟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至希坚,卻和暖如春边苹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背裁僧。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工个束, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聊疲。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓茬底,卻偏偏與公主長得像,于是被迫代替她去往敵國和親获洲。 傳聞我的和親對象是個(gè)殘疾皇子阱表,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容