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;
}
從運行結(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
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)存地址相同:
如果將__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é)果將不同*/
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對象
在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)存管理