內(nèi)存管理凝危!MRC
說一堆blabla(當(dāng)然啦些己,各種release的痛想必記憶猶新)亥啦,ARC
說一堆blabla...
多么老生常談的問題躯砰,想必每個(gè)開發(fā)者在起初不止一次的被問起這個(gè)問題枝恋,但是一旦被問起创倔,一般大家都會(huì)說起'黃金法則’,然后@property
鼓择,blabla一大堆…
大家都知道iOS的內(nèi)存管理是通過引用計(jì)數(shù)來管理的,當(dāng)引用計(jì)數(shù)為0的時(shí)候三幻,對(duì)象會(huì)自動(dòng)釋放掉,在ARC
環(huán)境下系統(tǒng)會(huì)“自動(dòng)管理”對(duì)象的引用計(jì)數(shù)呐能,然而事實(shí)上并非如此念搬,我們常常遇到試圖控制不能正常釋放問題,block
使用的時(shí)候內(nèi)存泄露.
好吧摆出,多么痛才能領(lǐng)悟朗徊,今天咱們就聊聊這個(gè)蛋疼的問題,當(dāng)然啦是在ARC環(huán)境下偎漫,分別從這么幾個(gè)地方聊聊
-
@property
— 幾個(gè)內(nèi)存管理相關(guān)的修飾符 -
self
— 大家有沒有想過爷恳,self
在ARC
下到底什么類型的,strong象踊?weak温亲? -
block
— 這玩兒大家每天都在寫,但是說起來他的內(nèi)存管理方式杯矩,不知道有多少能說全面的
Property
Property想必大家都寫過栈虚,當(dāng)然不排除還有很多一直在使用_ivar的,當(dāng)然啦他們之間的區(qū)別以及該用那個(gè)好咱們今天不聊史隆,如有想了解的同學(xué)請移步這里.屬性的內(nèi)存管理語義有 以下幾個(gè):
assign
:適用于基本數(shù)據(jù)類型魂务,其set
方法只會(huì)進(jìn)行簡單的賦值操作NSInteger
,CGFloat
...
/** 基本數(shù)據(jù)類型*/
@property (assign, nonatomic) NSInteger age;
strong
:強(qiáng)引用類型,被定義了該類型的屬性會(huì)被其所指對(duì)象持有,為這種屬性設(shè)置新值的時(shí)候,set
方法先保留新值,并釋放掉舊值,然后再把新值賦值上去
/** 適用于對(duì)象類型粘姜,并且被持有*/
@property (strong, nonatomic) UIView *view;
weak
:愛恨交織的weak
!弱引用類型鬓照,被定義了該類型的屬性不會(huì)被持有,在set
的時(shí)候,既不保留新值也不會(huì)釋放舊值孤紧,和assign
類似豺裆,不同的是在該屬性所指對(duì)象被銷毀之后,屬性值也會(huì)被置為nil
/** 弱引用類型,對(duì)象釋放后置為nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;
unsafe_unretained
:這個(gè)跟assign
就更像了,但不同的是它適用于對(duì)象類型.同樣的不會(huì)被持有,但與weak
不同是,所指對(duì)象被銷毀之后不會(huì)把屬性置為nil
/** 弱引用類型,對(duì)象釋放后不會(huì)置為nil*/
@property (unsafe_unretained, nonatomic) UIView *view;
copy
:它與所指對(duì)象是持有關(guān)系,與strong
類似,然而set
方法并不會(huì)保留新值,而是將其拷貝一份.所以,當(dāng)你聲明一個(gè)非可變集合類型(collection type)的時(shí)候應(yīng)該使用這個(gè)修飾符,不單單是NSString
,同樣的NSArray
,NSDictionary
,NSSet
也應(yīng)當(dāng)使用它來修飾,copy
可以保護(hù)非可變集合類型(collection type)的封裝性,因?yàn)樵谄鋫鬟f過程中有可能會(huì)指向一個(gè)可變類型(mutable type),此時(shí)如果不是copy
坛芽,那么設(shè)置完屬性之后,其值可能會(huì)在對(duì)象不知情的情況下被更改了.所以使用copy
修飾符,保證set
的時(shí)候copy
一份不可變(immutable)類型的值,確保前后的一致性.
/** 適用于集合類型(collection type)*/
@property (copy, nonatomic) NSString *name;
Self
看到這個(gè)self
估計(jì)很多人應(yīng)該開始吐槽了,這SB,self
有啥內(nèi)存管理可言留储?需要管理么?
哥負(fù)責(zé)任的告訴你,需要咙轩!需要!需要R跤薄;詈啊!
行量愧,你說需要就需要吧钾菊,哪里需要?Show me the code!!!
也許你可能會(huì)看到過這樣代碼偎肃,然后一頭霧水的就略過了...
#import "SSYNetwork.h"
- (void)startRequestData {
SSYNetwork *strongSelf = self;
[strongSelf.delegate finishedRequestData:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
}
我頭一次看到的時(shí)候也是一頭霧水煞烫,這是干嘛呢?寫得看起來很高大.仔細(xì)分析后就能夠發(fā)現(xiàn)累颂,這段代碼之所以增加一行strongSelf
,是為了防止提前釋放導(dǎo)致crash
.
這是一個(gè)請求網(wǎng)絡(luò)數(shù)據(jù)的start方法滞详,然而卻在里面調(diào)用了finish方法,其實(shí)還沒有等到start
方法執(zhí)行結(jié)束并返回的時(shí)候紊馏,self
已經(jīng)被finishedRequestData:
方法釋放,造成crash料饥,簡單的來說大致是這樣的:
- (void)clickAvatar {
// self持有delegate
[self.delegate clickAvatar]; // clickAvatar這個(gè)代理方法釋放了self
// 這個(gè)時(shí)候self成了野指針,然后就Boom??
}
那么現(xiàn)在我們可以考慮一下了朱监,在ARC里面self到底是個(gè)什么狀態(tài)?為何會(huì)這樣岸啡?
其實(shí)ARC
下,self
既不是strong
赫编,也不是weak
巡蘸,而是被我們忽略的unsafe_unretained
在我們調(diào)用方法的時(shí)候,ARC
不會(huì)對(duì)self
做retain
或release
,self
的生命周期由他的調(diào)用方法來決定
所以self
還是需要做一定的內(nèi)存管理擂送,不然一不小心就會(huì)??(Boom
)
Block
block
其實(shí)是我們幾乎每天都在用的東西悦荒,然而又讓人討厭的東西,之所以討厭团甲,是因?yàn)闆]有做好內(nèi)存管理逾冬,產(chǎn)生循環(huán)引用導(dǎo)致內(nèi)存泄露甚至是crash
,下面咱們就嘮嘮這個(gè)煩人的玩意兒
聲明方式
// 局部變量
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...}
// 屬性,作為屬性的時(shí)候其修飾符一定是copy
@property (nonatomic, copy) returnType (^blockName)(parameters);
// 作為參數(shù)
- (void)someMethodThatTakesABlock:(returnType (^)(parameters))blockName;
// 方法調(diào)用是作為參數(shù)
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
// 宏定義
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
循環(huán)引用
在使用block
的時(shí)候產(chǎn)生了循環(huán)引用是最讓人頭疼的,那么為什么會(huì)產(chǎn)生循環(huán)引用呢?先看看產(chǎn)生循環(huán)引用的代碼長什么樣身腻,再分析分析why?
// 兩個(gè)對(duì)象間相互引用
@property (nonatomic, copy) void(^block)(void);
self.block = ^{
[self doSomething];
};
// 多個(gè)對(duì)象間相互引用,多對(duì)象相互引用更難以發(fā)現(xiàn)
ClassA* objA = [[ClassA alloc] init];
self.block = ^{
[self doSomething];
};
self.objA = objA;
上面的這段代碼中self
和block
之間相互持有,在block
里面調(diào)用的doSomething
方法,使得self
引用計(jì)數(shù)+1
,這個(gè)過程中self
是不會(huì)被釋放,這就導(dǎo)致了循環(huán)引用.OK产还,那么問題來了,如何解決呢嘀趟?
其實(shí)脐区,解決這個(gè)問題就在于如何讓block不持有self,要解決這個(gè)問題她按,只需要使用__weak
聲明一個(gè)弱引用(weakSelf
)就可以了,代碼如下:
@property (nonatomic, copy) void(^block)(void);
// 弱引用
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething];
};
搞定牛隅,這樣之后我們再調(diào)用dealloc
方法,打上斷點(diǎn),就可以看出self
已經(jīng)被釋放了
Wow!??????????
不要天真的以為這樣就萬事大吉,就可以丟下鼠標(biāo)等勝利了!
如果我說,有時(shí)候即使你使用了__weak
做了弱引用,也還是有crash的危險(xiǎn)!
請看下面的代碼:
// 弱引用
__weak typeof(self) weakSelf = self;
// 異步線程,這里的block是作為一個(gè)參數(shù)的
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.xx = xx;
});
讓我們分析一下原因,將block作為參數(shù)傳遞給dispatch_async
時(shí),系統(tǒng)會(huì)將block
拷貝到堆(heap
)上,如果block
中使用了self
持有的對(duì)象,就會(huì)retain self
,因?yàn)?code>dispatch_async并不知道self
會(huì)在什么時(shí)候被釋放,所以為了確保系統(tǒng)調(diào)度執(zhí)行block
中的任務(wù)時(shí)self
沒有被意外釋放掉,dispatch_async
必須自己retain
一次self
,任務(wù)完成后在release self
.但是這里使用了__weak
,使得dispatch_async
沒有增加self
的引用計(jì)數(shù),這就導(dǎo)致了在調(diào)度執(zhí)行block
之前,self
可能已經(jīng)被釋放掉了,然后就crash??,控制臺(tái)提示BAD_ACCESS
好吧酌泰,算你對(duì)了媒佣,那你說該咋辦?
嘿嘿陵刹,既然沒有retain
默伍,那就想辦法retain
唄?代碼如下:
// 弱引用
__weak typeof(self) weakSelf = self;
// 異步線程,這里的block是作為一個(gè)參數(shù)的
dispatch_async(dispatch_get_main_queue(), ^{
// 加強(qiáng)引用防止提前釋放,并且做判斷
__strong typeof(weakSelf) strongSelf = self;
if (strongSelf) {
strongSelf.xx = xx;
}
});
然而這樣寫就好了么衰琐?還有沒有更好的方法也糊?程序員都是有追求的,我們是一群高逼格的從業(yè)者羡宙!
好吧狸剃,我只是想說還有更優(yōu)雅的方式來寫下面兩句話
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
有使用過ReactiveCocoa的同學(xué)應(yīng)該知道,其實(shí)我們可以使用影子變量@weakify/@strongify
這種方式同樣能解決,至于關(guān)于影子變量的詳細(xì)介紹這里就不多說了,有興趣的同學(xué)可以到這里看看,使用影子變量后,代碼就會(huì)像下面這樣:
@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@strongify(self);
if (!self) return;
self.xx = xx; // 這里就可以放心的使用self,簡單粗暴
});
OK,裝逼結(jié)束9啡取3佟!
如有錯(cuò)誤之處斗搞,歡迎討論.