iOS內(nèi)功心法--內(nèi)存管理機制

Artwork

There is something inside ,that they can't get to , that they can't touch. That's yours姑曙!

前言:

移動app開發(fā)中埠胖,由于移動設(shè)備內(nèi)存的限制崎场,內(nèi)存管理是一個非常重要的話題。我們知道在程序運行過程中要創(chuàng)建大量的對象嗅战,和其他高級語言類似,在ObjC中對象時存儲在堆中的招刨,系統(tǒng)并不會自動釋放堆中的內(nèi)存(注意基本類型是由系統(tǒng)自己管理的暖释,放在棧上)。如果一個對象創(chuàng)建并使用后沒有得到及時釋放那么就會占用大量內(nèi)存剑刑。其他高級語言如C#媳纬、Java都是通過垃圾回收來(GC)解決這個問題的,但在OjbC中并沒有類似的垃圾回收機制叛甫,因此它的內(nèi)存管理就需要由開發(fā)人員手動維護层宫。今天將著重介紹ObjC內(nèi)存管理规肴。

1. ARC 和 非ARC

oc的內(nèi)存管理方式彪见,分為ARC(automatic reference counting自動引用計數(shù))和非ARC模式。Apple 在 Xcode 4.2 中發(fā)布了 Automatic Reference Counting (ARC)讥珍,該功能為開發(fā)人員消除了手動執(zhí)行引用計數(shù)的負擔(dān)抖苦。

目前xcode新建項目毁菱,都會推薦默認的ARC方式(ARC的確有很大的優(yōu)勢)。當(dāng)然锌历,如果必須要使用非ARC贮庞,可以在build setting中修改automatic reference counting選項為NO。如果在ARC項目中引入非ARC的代碼或者靜態(tài)庫究西,需要在build phases設(shè)置相應(yīng)資源為-fno-objc-ar窗慎;相反,非arc項目設(shè)置arc使用-fobjc-arc卤材。

ARC與非ARC遮斥,從字面上來看,在于是否auto扇丛,即是由編譯器來自動實現(xiàn)reference counting术吗,還是由開發(fā)者手動完成引用計數(shù)的加減。作為現(xiàn)在經(jīng)常使用arc模式來開發(fā)的我們來說帆精,ARC大大減少了我們對內(nèi)存管理的工作较屿,甚至隧魄,很多入門開發(fā)者完全像開發(fā)java應(yīng)用一樣,沒有管object的釋放隘蝎。然而對oc來說购啄,從mac os x10.8開始,garbage collector也已廢棄末贾,iOS上壓根就沒有出現(xiàn)過這個概念闸溃。iOS上oc的內(nèi)存管理,本質(zhì)上是引用計數(shù)的管理拱撵,理解引用計數(shù)辉川,是iOS內(nèi)存管理的核心。

2.引用計數(shù)器:

在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)機制,程序編譯時Xcode可以自動給你的代碼添加內(nèi)存釋放代碼拴测,如果編寫手動釋放代碼Xcode會報錯乓旗,因此在今天的內(nèi)容中如果你使用的是Xcode4.2之后的版本(相信現(xiàn)在大部分朋友用的版本都比這個要高),必須手動關(guān)閉ARC集索,這樣才有助于你理解ObjC的內(nèi)存回收機制屿愚。

ObjC中的內(nèi)存管理機制跟C語言中指針的內(nèi)容是同樣重要的,要開發(fā)一個程序并不難务荆,但是優(yōu)秀的程序則更測重于內(nèi)存管理妆距,它們往往占用內(nèi)存更少,運行更加流暢函匕。雖然在新版Xcode引入了ARC娱据,但是很多時候它并不能完全解決你的問題。在Xcode中關(guān)閉ARC:項目屬性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting設(shè)置為No即可盅惜。

3.內(nèi)存管理原理

在ObjC中沒有垃圾回收機制中剩,那么ObjC中內(nèi)存又是如何管理的呢?其實在ObjC中內(nèi)存的管理是依賴對象引用計數(shù)器來進行的:在ObjC中每個對象內(nèi)部都有一個與之對應(yīng)的整數(shù)(retainCount)抒寂,叫“引用計數(shù)器”结啼,當(dāng)一個對象在創(chuàng)建之后它的引用計數(shù)器為1,當(dāng)調(diào)用這個對象的alloc屈芜、retain郊愧、new、copy方法之后引用計數(shù)器自動在原來的基礎(chǔ)上加1(ObjC中調(diào)用一個對象的方法就是給這個對象發(fā)送一個消息)井佑,當(dāng)調(diào)用這個對象的release方法之后它的引用計數(shù)器減1糕珊,如果一個對象的引用計數(shù)器為0,則系統(tǒng)會自動調(diào)用這個對象的dealloc方法來銷毀這個對象毅糟。

下面通過一個簡單的例子看一下引用計數(shù)器的知識:
Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
#pragma mark - 屬性
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;

Person.m

#import "Person.h"

@implementation Person

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法,在這個方法中通常進行對象釋放操作
-(void)dealloc{
    NSLog(@"Invoke Person's dealloc method.");
    [super dealloc];//注意最后一定要調(diào)用父類的dealloc方法(兩個目的:一是父類可能有其他引用對象需要釋放澜公;二是:當(dāng)前對象真正的釋放操作是在super的dealloc中完成的)
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"

void Test1(){
    Person *p=[[Person alloc]init]; //調(diào)用alloc姆另,引用計數(shù)器+1
    p.name=@"Kenshin";
    p.age=28;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結(jié)果:retainCount=1
    
    [p release];
    //結(jié)果:Invoke Person's dealloc method.
 
    //上面調(diào)用過release方法喇肋,p指向的對象就會被銷毀,但是此時變量p中還存放著Person對象的地址迹辐,
    //如果不設(shè)置p=nil,則p就是一個野指針明吩,它指向的內(nèi)存已經(jīng)不屬于這個程序间学,因此是很危險的
    p=nil;
    //如果不設(shè)置p=nil,此時如果再調(diào)用對象release會報錯印荔,但是如果此時p已經(jīng)是空指針了低葫,
    //則在ObjC中給空指針發(fā)送消息是不會報錯的
    [p release];
}

void Test2(){
    Person *p=[[Person alloc]init];
    p.name=@"Kenshin";
    p.age=28;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結(jié)果:retainCount=1
    
    [p retain];//引用計數(shù)器+1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結(jié)果:retainCount=2
    
    [p release];//調(diào)用1次release引用計數(shù)器-1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結(jié)果:retainCount=1
    [p release];
    //結(jié)果:Invoke Person's dealloc method.
    p=nil;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test1();
    }
    return 0;
}

在上面的代碼中我們可以通過dealloc方法來查看是否一個對象已經(jīng)被回收,如果沒有被回收則有可能造成內(nèi)存泄露仍律。如果一個對象被釋放之后嘿悬,那么最后引用它的變量我們手動設(shè)置為nil,否則可能造成野指針錯誤水泉,而且需要注意在ObjC中給空對象發(fā)送消息是不會引起錯誤的善涨。

野指針錯誤形式在Xcode中通常表現(xiàn)為:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)錯誤。因為你訪問了一塊已經(jīng)不屬于你的內(nèi)存草则。

4. 內(nèi)存釋放的原則

手動管理內(nèi)存有時候并不容易钢拧,因為對象的引用有時候是錯綜復(fù)雜的,對象之間可能互相交叉引用炕横,此時需要遵循一個法則:誰創(chuàng)建源内,誰釋放。

假設(shè)現(xiàn)在有一個人員Person類看锉,每個Person可能會購買一輛汽車Car姿锭,通常情況下購買汽車這個活動我們可能會單獨抽取到一個方法中,同時買車的過程中我們可能會多看幾輛來最終確定理想的車伯铣,現(xiàn)在我們的代碼如下:

Car.h

#import <Foundation/Foundation.h>

@interface Car : NSObject

#pragma mark - 屬性
#pragma mark 車牌號
@property (nonatomic,copy) NSString *no;

#pragma mark - 公共方法
#pragma mark 運行方法
-(void)run;

@end

Car.m


#import "Car.h"

@implementation Car

#pragma mark - 公共方法
#pragma mark 運行方法
-(void)run{
    NSLog(@"Car(%@) run.",self.no);
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    
    NSLog(@"Invoke Car(%@) dealloc method.",self.no);
    [super dealloc];
}
@end

Person.h

#import <Foundation/Foundation.h>
@class Car;

@interface Person : NSObject{
    Car *_car;
}
#pragma mark - 屬性
#pragma mark 姓名
@property (nonatomic,copy) NSString *name;

#pragma mark - 公共方法
#pragma mark Car屬性的set方法
-(void)setCar:(Car *)car;
#pragma mark  Car屬性的get方法
-(Car *)car;
@end

Person.m

#import "Person.h"
#import "Car.h"

@implementation Person

#pragma mark - 公共方法
#pragma mark Car屬性的set方法
-(void)setCar:(Car *)car{
    if (_car!=car) { //首先判斷要賦值的變量和當(dāng)前成員變量是不是同一個變量
        [_car release]; //釋放之前的對象
        _car=[car retain];//賦值時重新retain
    }
}
#pragma mark  Car屬性的get方法
-(Car *)car{
    return _car;
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    NSLog(@"Invoke Person(%@) dealloc method.",self.name);
    [_car release];//在此釋放對象呻此,即使沒有賦值過由于空指針也不會出錯
    [super dealloc];
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"

void getCar(Person *p){
    Car *car1=[[Car alloc]init];
    car1.no=@"888888";
    
    p.car=car1;
    
    NSLog(@"retainCount(p)=%lu",[p retainCount]);
    
    Car *car2=[[Car alloc]init];
    car2.no=@"666666";
    
    [car1 release];
    car1=nil;
    
    [car2 release];
    car2=nil;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p=[[Person alloc]init];
        p.name=@"Kenshin";
        
        getCar(p);
        
        [p.car run];
        
        [p release];
        
        p=nil;
        
    }
    return 0;
}
261710001826308.png

從運行結(jié)果來看創(chuàng)建的三個對象p、car1腔寡、car2都被回收了焚鲜,而且[p.car run]也能順利運行,已經(jīng)達到了我們的需求放前。但是這里需要重點解釋一下setCar方法的實現(xiàn),setCar方法中為什么沒有寫成如下形式:

-(void)setCar:(Car *)car{
    _car=car;
}

前面在我們說到屬性的定義時不是都采用的這種方式嗎忿磅?

根據(jù)前面說到的內(nèi)存釋放原則,getCar方法完全符合凭语,在這個方法中定義的兩個對象car1葱她、car2也都是在這個方法中釋放的,包括main函數(shù)中的p對象也是在main函數(shù)中定義和釋放的似扔。但是如果發(fā)現(xiàn)調(diào)用完getCar方法之后緊接著調(diào)用了汽車的run方法吨些,當(dāng)然這在程序設(shè)計和開發(fā)過程中應(yīng)該是再普通不過的設(shè)計了搓谆。如果setCar寫成“_car=car”的形式,當(dāng)調(diào)用完getCar方法后豪墅,人員的car屬性被釋放了泉手,此時調(diào)用run方法是會報錯的(大家自己可以試試)。但是如下的方式卻不會有問題:

-(void)setCar:(Car *)car{
    if (_car!=car) { //首先判斷要賦值的變量和當(dāng)前成員變量是不是同一個變量
        [_car release]; //釋放之前的對象
        _car=[car retain];//賦值時重新retain
    }
}

因為在這個方法中我們通過[car retain]保證每次屬性賦值的時候?qū)ο笠糜嫈?shù)器+1,這樣一來調(diào)用過getCar方法可以保證人員的car屬性不會被釋放偶器,其次為了保證上一次的賦值對象(car1)能夠正常釋放斩萌,我們在賦新值之前對原有的值進行release操作。最后在Person的dealloc方法中對_car進行一次release操作(因為setCar中做了一次retain操作)保證_car能正称梁洌回收颊郎。

5.屬性的所有權(quán)語義(ownership semantic)

@property (strong, nonatomic) UIWindow *window;
  strong, nonatomic
這些屬性修飾語義,表征了屬性的特性亭枷。oc的編譯器會為屬性自動生成get和set方法袭艺,而這兩個方法會涉及對象所有權(quán)的保留與釋放,諸如strong/weak這些不同的屬性修飾符會造成不同的內(nèi)存操作叨粘。ARC和非ARC模式下修飾符是不同的猾编。

非ARC

語義 說明
retain 保留新值,釋放舊值升敲,將新值賦給對象答倡。引用計數(shù)+1
assign 簡單賦值,共享同一塊內(nèi)存地址驴党。引用計數(shù)不變
copy 建立一個新的引用計數(shù)為1的對象瘪撇,然后釋放舊對象。

ARC

語義 說明
strong 表明擁有對象港庄,同retain倔既。
weak 表明非擁有對象,同assign類似鹏氧,但當(dāng)對象被摧毀后渤涌,對象會被清空(變?yōu)閚il)
assign 同非ARC中assign,設(shè)置方法只會針對“純量類型”,如NSInteger,CGFloat等
copy 同非ARC中copy
unsafe_unretained 同assign把还,但適用于“對象類型”实蓬,表明非擁有對象,但當(dāng)對象摧毀后吊履,對象不會自動清空安皱,即可能會EXC_BAD_ACCESS。

6艇炎、內(nèi)存管理-黃金法則

The basic rule to apply is everything that increases the reference counter with alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.

如果對一個對象使用了alloc酌伊、[mutable]copy、retain缀踪,那么你必須使用相應(yīng)的release或者autorelease居砖。

類型定義:
基本類型:任何C的類型燕锥,如:int、short悯蝉、char、long托慨、struct鼻由、enum、union等屬于基本類型或者結(jié)構(gòu)體厚棵;
內(nèi)存管理對于C語言基本類型無效蕉世;
任何繼承與NSObject類的對象都屬于OC類型。
所有OC對象都有一個計數(shù)器婆硬,保留著當(dāng)前被引用的數(shù)量狠轻。

內(nèi)存管理對象:
OC的對象:凡是繼承于NSObject;
每一個對象都有一個retainCount計數(shù)器彬犯。表示當(dāng)前的被應(yīng)用的計數(shù)向楼。如果計數(shù)為0,那么就真正的釋放這個對象谐区。

alloc湖蜕、retain、release函數(shù):

  1)alloc 函數(shù)是創(chuàng)建對象使用宋列,創(chuàng)建完成后計數(shù)器為1昭抒;只用1次。

  2)retain是對一個對象的計數(shù)器+1炼杖;可以調(diào)用多次灭返。

  3)release是對一個對象計數(shù)器-1;減到0對象就會從內(nèi)存中釋放坤邪。

 增加對象計數(shù)器的三種方式:

  1)當(dāng)明確使用alloc方法來分配對象熙含;

  2)當(dāng)明確使用copy[WithZone:]或者mutableCopy[WithZone:]來copy對象的時;

  3)當(dāng)明確使用retain消息罩扇。

上述三種方法使得計數(shù)器增加婆芦,那么就需要使用[auto]release來明確釋放對象,也就是遞減計數(shù)器喂饥。

copy屬性:copy屬性是完全把對象重新拷貝一份消约,計數(shù)器重新設(shè)置為1,和之前拷貝的數(shù)據(jù)完全脫離關(guān)系员帮。

7.ARC的本質(zhì)

ARC是編譯器(時)特性或粮,而不是運行時特性,更不是垃圾回收器(GC)捞高。

Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.

ARC只是相對于MRC(Manual Reference Counting或稱為非ARC氯材,下文中我們會一直使用MRC來指代非ARC的管理方式)的一次改進渣锦,但它和之前的技術(shù)本質(zhì)上沒有區(qū)別。具體信息可以參考ARC編譯器官方文檔氢哮。

ARC的開啟與關(guān)閉
不同于XCode4可以在創(chuàng)建工程時選擇關(guān)閉ARC袋毙,XCode5在創(chuàng)建的工程是默認開啟ARC,沒有可以關(guān)閉ARC的選項冗尤。

如果需要對特定文件開啟或關(guān)閉ARC听盖,可以在工程選項中選擇Targets -> Compile Phases -> Compile Sources,在里面找到對應(yīng)文件裂七,添加flag:

打開ARC:-fobjc-arc
關(guān)閉ARC:-fno-objc-arc

如圖:
311559437226630.png

ARC的修飾符
ARC主要提供了4種修飾符皆看,他們分別是:__strong,__weak,__autoreleasing,__unsafe_unretained。

__strong
表示引用為強引用背零。對應(yīng)在定義property時的"strong"腰吟。所有對象只有當(dāng)沒有任何一個強引用指向時,才會被釋放徙瓶。
注意:如果在聲明引用時不加修飾符毛雇,那么引用將默認是強引用。當(dāng)需要釋放強引用指向的對象時倍啥,需要將強引用置nil禾乘。

舉例:如果我想創(chuàng)建一個字符串,使用完之后將它釋放調(diào)用虽缕,使用MRC管理內(nèi)存的寫法應(yīng)該是這樣:

{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"對象的RC=1

    NSLog(@"%@", text);

    [text release];                      //@"Hello, world"對象的RC=0

}

而如果是使用ARC方式的話始藕,就text對象無需調(diào)用release方法,而是當(dāng)text變量超過作用域時氮趋,編譯器來自動加入[text release]方法來釋放內(nèi)存

{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"對象的RC=1

    NSLog(@"%@", text);

}

/*

 *  當(dāng)text超過作用域時伍派,@"Hello, world"對象會自動釋放,RC=0

 */
而當(dāng)你將text賦值給其他變量anotherText時剩胁,MRC需要retain一下來持有所有權(quán)诉植,當(dāng)text和anotherText使用完之后,各個調(diào)用release方法來釋放昵观。


{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"對象的RC=1

    NSLog(@"%@", text);

     

    NSString *anotherText = text;        //@"Hello, world"對象的RC=1

    [anotherText retain];                //@"Hello, world"對象的RC=2

    NSLog(@"%@", anotherText);

     

    [text release];                      //@"Hello, world"對象的RC=1

    [anotherText release];               //@"Hello, world"對象的RC=0

}
而使用ARC的話晾腔,并不需要調(diào)用retain和release方法來持有跟釋放對象。


{

    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"對象的RC=1

    NSLog(@"%@", text);

     

    NSString *anotherText = text;        //@"Hello, world"對象的RC=2

    NSLog(@"%@", anotherText);

}

/*

 *  當(dāng)text和anotherText超過作用域時啊犬,會自動調(diào)用[text release]和[anotherText release]方法灼擂, @"Hello, world"對象的RC=0

 */

__weak
其實編譯器根據(jù)__strong修飾符來管理對象內(nèi)存。但是__strong并不能解決引用循環(huán)(Reference Cycle)問題:對象A持有對象B觉至,反過來剔应,對象B持有對象A;這樣會導(dǎo)致不能釋放內(nèi)存造成內(nèi)存泄露問題。

表示引用為弱引用峻贮。對應(yīng)在定義property時用的"weak"席怪。弱引用不會影響對象的釋放,即只要對象沒有任何強引用指向纤控,即使有100個弱引用對象指向也沒用挂捻,該對象依然會被釋放。不過好在船万,對象在被釋放的同時细层,指向它的弱引用會自動被置nil,這個技術(shù)叫zeroing weak pointer唬涧。這樣有效得防止無效指針、野指針的產(chǎn)生盛撑。__weak一般用在delegate關(guān)系中防止循環(huán)引用或者用來修飾指向由Interface Builder編輯與生成的UI控件碎节。

舉一個簡單的例子,有一個類Test有個屬性objc抵卫,有兩個對象test1和test2的屬性objc互相引用test1和test2:


@interface Test : NSObject

@property (strong, nonatomic) id objc;

@end

{

    Test *test1 = [Test new];        /* 對象a */

    /* test1有一個強引用到對象a */

    
    Test *test2 = [Test new];        /* 對象b */

    /* test2有一個強引用到對象b */

    
    test1.objc = test2;              /* 對象a的成員變量objc有一個強引用到對象b */

    test2.objc = test1;              /* 對象b的成員變量objc有一個強引用到對象a */

}

/*   當(dāng)變量test1超過它作用域時狮荔,它指向a對象會自動release

 *   當(dāng)變量test2超過它作用域時,它指向b對象會自動release

 *   

 *   此時介粘,b對象的objc成員變量仍持有一個強引用到對象a

 *   此時殖氏,a對象的objc成員變量仍持有一個強引用到對象b

 *   于是發(fā)生內(nèi)存泄露

 */

如何解決?于是我們引用一個__weakownership qualifier姻采,被它修飾的變量都不持有對象的所有權(quán)雅采,而且當(dāng)變量指向的對象的RC為0時,變量設(shè)置為nil慨亲。例如:

__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
NSLog(@"%@", text);

由于text變量被__weak修飾婚瓜,text并不持有@"Sam Lau"對象的所有權(quán),@"Sam Lau"對象一創(chuàng)建就馬上被釋放刑棵,并且編譯器給出警告巴刻,所以打印結(jié)果為(null)。

所以蛉签,針對剛才的引用循環(huán)問題胡陪,只需要將Test類的屬性objc設(shè)置weak修飾符,那么就能解決碍舍。

@interface Test : NSObject

@property (weak, nonatomic) id objc;

@end

{

    Test *test1 = [Test new];        /* 對象a */

    /* test1有一個強引用到對象a */

     

    Test *test2 = [Test new];        /* 對象b */

    /* test2有一個強引用到對象b */

     

    test1.objc = test2;              /* 對象a的成員變量objc不持有對象b */

    test2.objc = test1;              /* 對象b的成員變量objc不持有對象a */

}

/*   當(dāng)變量test1超過它作用域時柠座,它指向a對象會自動release

 *   當(dāng)變量test2超過它作用域時,它指向b對象會自動release

 */

__autoreleasing

表示在autorelease pool中自動釋放對象的引用乒验,和MRC時代autorelease的用法相同愚隧。定義property時不能使用這個修飾符,任何一個對象的property都不應(yīng)該是autorelease型的。

一個常見的誤解是狂塘,在ARC中沒有autorelease录煤,因為這樣一個“自動釋放”看起來好像有點多余。這個誤解可能源自于將ARC的“自動”和autorelease“自動”的混淆荞胡。其實你只要看一下每個iOS App的main.m文件就能知道妈踊,autorelease不僅好好的存在著,并且變得更fashion了:不需要再手工被創(chuàng)建泪漂,也不需要再顯式得調(diào)用[drain]方法釋放內(nèi)存池廊营。

__unsafe_unretained

__unsafe_unretained ownership qualifier,正如名字所示萝勤,它是不安全的露筒。它跟__weak相似,被它修飾的變量都不持有對象的所有權(quán)敌卓,但當(dāng)變量指向的對象的RC為0時慎式,變量并不設(shè)置為nil,而是繼續(xù)保存對象的地址趟径;這樣的話瘪吏,對象有可能已經(jīng)釋放,但繼續(xù)訪問蜗巧,就會造成非法訪問(Invalid Access)掌眠。例子如下:

__unsafe_unretained id obj0 = nil;

 

{

    id obj1 = [[NSObject alloc] init];     // 對象A

    /* 由于obj1是強引用,所以obj1持有對象A的所有權(quán)幕屹,對象A的RC=1 */

    obj0 = obj1;

    /* 由于obj0是__unsafe_unretained蓝丙,它不持有對象A的所有權(quán),但能夠引用它望拖,對象A的RC=1 */

    NSLog(@"A: %@", obj0);

}

/* 當(dāng)obj1超過它的作用域時迅腔,它指向的對象A將會自動釋放 */

 

NSLog(@"B: %@", obj0);

/* 由于obj0是__unsafe_unretained,當(dāng)它指向的對象RC=0時靠娱,它會繼續(xù)保存對象的地址沧烈,所以兩個地址相同 */

打印結(jié)果是內(nèi)存地址相同:

1455700144595337.png

如果將__unsafe_unretained改為weak的話,兩個打印結(jié)果將不同

__weak id obj0 = nil;

{

    id obj1 = [[NSObject alloc] init];     // 對象A

    /* 由于obj1是強引用像云,所以obj1持有對象A的所有權(quán)锌雀,對象A的RC=1 */

    obj0 = obj1;

    /* 由于obj0是__unsafe_unretained,它不持有對象A的所有權(quán)迅诬,但能夠引用它腋逆,對象A的RC=1 */

    NSLog(@"A: %@", obj0);

}

/* 當(dāng)obj1超過它的作用域時,它指向的對象A將會自動釋放 */

 

NSLog(@"B: %@", obj0);

/* 由于obj0是__weak侈贷, 當(dāng)它指向的對象RC=0時惩歉,它會自動設(shè)置為nil,所以兩個打印結(jié)果將不同*/
1455700201721033.png

ARC是在iOS 5引入的,而這個修飾符主要是為了在ARC剛發(fā)布時兼容iOS 4以及版本更低的設(shè)備撑蚌,因為這些版本的設(shè)備沒有weak pointer system上遥,簡單的理解這個系統(tǒng)就是我們上面講weak時提到的,能夠在weak引用指向?qū)ο蟊会尫藕笳浚岩弥底詣釉O(shè)為nil的系統(tǒng)粉楚。這個修飾符在定義property時對應(yīng)的是"unsafe_unretained",實際可以將它理解為MRC時代的assign:純粹只是將引用指向?qū)ο罅恋妫瑳]有任何額外的操作模软,在指向?qū)ο蟊会尫艜r依然原原本本地指向原來被釋放的對象(所在的內(nèi)存區(qū)域)。所以非常不安全饮潦。
現(xiàn)在可以完全忽略掉這個修飾符了燃异,因為iOS 4早已退出歷史舞臺很多年。*使用修飾符的正確姿勢(方式=继蜡。=)

這可能是很多人都不知道的一個問題特铝,包括之前的我,但卻是一個特別要注意的問題壹瘟。
蘋果的文檔中明確地寫道:

You should decorate variables correctly. When using qualifiers in an object variable declaration,

the correct format is:

ClassName * qualifier variableName;

按照這個說明,要定義一個weak型的NSString引用鳄逾,它的寫法應(yīng)該是:

NSString * __weak str = @"hehe"; // 正確稻轨!
__weak NSString *str = @"hehe";  // 錯誤!

自動釋放池

在開發(fā)中雕凹,我們常常都會使用到局部變量殴俱,局部變量一個特點就是當(dāng)它超過作用域時,就會自動釋放枚抵。而autorelease pool跟局部變量類似线欲,當(dāng)執(zhí)行代碼超過autorelease pool塊時,所有放在autorelease pool的對象都會自動調(diào)用release汽摹。它的工作原理如下:
1.創(chuàng)建一個NSAutoreleasePool對象
2.autorelease pool塊的對象調(diào)用autorelease方法
3.釋放NSAutoreleasePool對象

1455699554550339.png

在ObjC中也有一種內(nèi)存自動釋放的機制叫做“自動引用計數(shù)”(或“自動釋放池”)李丰,與C#、Java不同的是逼泣,這只是一種半自動的機制趴泌,有些操作還是需要我們手動設(shè)置的。自動內(nèi)存釋放使用@autoreleasepool關(guān)鍵字聲明一個代碼塊拉庶,如果一個對象在初始化時調(diào)用了autorelase方法嗜憔,那么當(dāng)代碼塊執(zhí)行完之后,在塊中調(diào)用過autorelease方法的對象都會自動調(diào)用一次release方法氏仗。這樣一來就起到了自動釋放的作用吉捶,同時對象的銷毀過程也得到了延遲(統(tǒng)一調(diào)用release方法)。看下面的代碼:

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

#pragma mark - 屬性
#pragma mark 姓名
@property (nonatomic,copy) NSString *name;

#pragma mark - 公共方法
#pragma mark 帶參數(shù)的構(gòu)造函數(shù)
-(Person *)initWithName:(NSString *)name;
#pragma mark 取得一個對象(靜態(tài)方法)
+(Person *)personWithName:(NSString *)name;

Person.m

#import "Person.h"

@implementation Person

#pragma mark - 公共方法
#pragma mark 帶參數(shù)的構(gòu)造函數(shù)
-(Person *)initWithName:(NSString *)name{
    if(self=[super init]){
        self.name=name;
    }
    return self;
}
#pragma mark 取得一個對象(靜態(tài)方法)
+(Person *)personWithName:(NSString *)name{
    Person *p=[[[Person alloc]initWithName:name] autorelease];//注意這里調(diào)用了autorelease
    return p;
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    NSLog(@"Invoke Person(%@) dealloc method.",self.name);
    [super dealloc];
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"


int main(int argc, const char * argv[]) {

    @autoreleasepool {
        Person *person1=[[Person alloc]init];
        [person1 autorelease];//調(diào)用了autorelease方法后面就不需要手動調(diào)用release方法了
        person1.name=@"Kenshin";//由于autorelease是延遲釋放呐舔,所以這里仍然可以使用person1
        
        Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//調(diào)用了autorelease方法
        
        Person *person3=[Person personWithName:@"rosa"];//內(nèi)部已經(jīng)調(diào)用了autorelease币励,所以不需要手動釋放,這也符合內(nèi)存管理原則滋早,因為這里并沒有alloc所以不需要release或者autorelease
        
        Person *person4=[Person personWithName:@"jack"];
        [person4 retain];
    }
    /*結(jié)果:
     Invoke Person(rosa) dealloc method.
     Invoke Person(Kaoru) dealloc method.
     Invoke Person(Kenshin) dealloc method.
     */
    
    return 0;
}

當(dāng)上面@autoreleaespool代碼塊執(zhí)行完之后榄审,三個對象都得到了釋放,但是person4并沒有釋放杆麸,原因很簡單搁进,由于我們手動retain了一次,當(dāng)自動釋放池釋放后調(diào)用四個對的release方法昔头,當(dāng)調(diào)用完person4的release之后它的引用計數(shù)器為1饼问,所有它并沒有釋放(這是一個反例,會造成內(nèi)存泄露)揭斧;autorelase方法將一個對象的內(nèi)存釋放延遲到了自動釋放池銷毀的時候莱革,因此上面person1,調(diào)用完autorelase之后它還存在讹开,因此給name賦值不會有任何問題盅视;在ObjC中通常如果一個靜態(tài)方法返回一個對象本身的話,在靜態(tài)方法中我們需要調(diào)用autorelease方法旦万,因為按照內(nèi)存釋放原則闹击,在外部使用時不會進行alloc操作也就不需要再調(diào)用release或者autorelase,所以這個操作需要放到靜態(tài)方法內(nèi)部完成成艘。

對于自動內(nèi)存釋放簡單總結(jié)一下:
autorelease方法不會改變對象的引用計數(shù)器赏半,只是將這個對象放到自動釋放池中;
自動釋放池實質(zhì)是當(dāng)自動釋放池銷毀后調(diào)用對象的release方法淆两,不一定就能銷毀對象(例如如果一個對象的引用計數(shù)器>1則此時就無法銷毀)断箫;
由于自動釋放池最后統(tǒng)一銷毀對象,因此如果一個操作比較占用內(nèi)存(對象比較多或者對象占用資源比較多)秋冰,最好不要放到自動釋放池或者考慮放到多個自動釋放池仲义;
ObjC中類庫中的靜態(tài)方法一般都不需要手動釋放,內(nèi)部已經(jīng)調(diào)用了autorelease方法剑勾;

8. block的內(nèi)存管理

iOS中使用block必須自己管理內(nèi)存,錯誤的內(nèi)存管理將導(dǎo)致循環(huán)引用等內(nèi)存泄漏問題甥材,這里主要說明在ARC下block聲明和使用的時候需要注意的兩點:
  1)如果你使用@property去聲明一個block的時候盯另,一般使用copy來進行修飾(當(dāng)然也可以不寫,編譯器自動進行copy操作)洲赵,盡量不要使用retain鸳惯。

 @property (nonatomic, copy) void(^block)(NSData * data);

2)block會對內(nèi)部使用的對象進行強引用商蕴,因此在使用的時候應(yīng)該確定不會引起循環(huán)引用,當(dāng)然保險的做法就是添加弱引用標記芝发。

 __weak typeof(self) weakSelf = self;

[參考鏈接](http://blog.csdn.net/shaobo8910/article/details/55051846
iOS開發(fā)ARC內(nèi)存管理技術(shù)要點
iOS開發(fā)系列—Objective-C之內(nèi)存管理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绪商,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辅鲸,更是在濱河造成了極大的恐慌格郁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件独悴,死亡現(xiàn)場離奇詭異例书,居然都是意外死亡,警方通過查閱死者的電腦和手機刻炒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門决采,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坟奥,你說我怎么就攤上這事树瞭。” “怎么了爱谁?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵晒喷,是天一觀的道長。 經(jīng)常有香客問我访敌,道長凉敲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任捐顷,我火速辦了婚禮,結(jié)果婚禮上雨效,老公的妹妹穿的比我還像新娘迅涮。我一直安慰自己,他們只是感情好徽龟,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布叮姑。 她就那樣靜靜地躺著,像睡著了一般据悔。 火紅的嫁衣襯著肌膚如雪传透。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天极颓,我揣著相機與錄音朱盐,去河邊找鬼。 笑死菠隆,一個胖子當(dāng)著我的面吹牛兵琳,可吹牛的內(nèi)容都是我干的狂秘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼躯肌,長吁一口氣:“原來是場噩夢啊……” “哼者春!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起清女,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤钱烟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嫡丙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拴袭,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年迄沫,在試婚紗的時候發(fā)現(xiàn)自己被綠了稻扬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡羊瘩,死狀恐怖泰佳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尘吗,我是刑警寧澤逝她,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站睬捶,受9級特大地震影響黔宛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擒贸,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一臀晃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧介劫,春花似錦徽惋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至誉碴,卻和暖如春宦棺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背黔帕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工代咸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人成黄。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓侣背,卻偏偏與公主長得像白华,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贩耐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 29.理解引用計數(shù) Objective-C語言使用引用計數(shù)來管理內(nèi)存弧腥,也就是說,每個對象都有個可以遞增或遞減的計數(shù)...
    Code_Ninja閱讀 1,470評論 1 3
  • 1.1 什么是自動引用計數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,081評論 1 17
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機制。與retain配對使用的方法是dealloc還是release铡买,為什么更鲁?需要與a...
    丶逐漸閱讀 1,948評論 1 16
  • 前言 現(xiàn)在iOS開發(fā)已經(jīng)是arc甚至是swift的時代,但是內(nèi)存管理仍是一個重點關(guān)注的問題奇钞,如果只知盲目開發(fā)而不知...
    明仔Su閱讀 26,531評論 16 175
  • 內(nèi)存管理是程序在運行時分配內(nèi)存澡为、使用內(nèi)存,并在程序完成時釋放內(nèi)存的過程景埃。在Objective-C中媒至,也被看作是在眾...
    蹲瓜閱讀 3,001評論 1 8