40.用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)

《編寫(xiě)高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》--第六章 第40條
(ps:此乃讀書(shū)筆記留瞳,加深記憶,僅供大家參考)


第40條:用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)

使用塊時(shí)肾筐,若不仔細(xì)思量,則很容易導(dǎo)致“保留環(huán)”(retain cycle)。在啟動(dòng)獲取器時(shí)瑞眼,可設(shè)置completion handler,這個(gè)塊會(huì)在下載結(jié)束之后以回調(diào)方式執(zhí)行棵逊。為了能在下載完成后通過(guò)p_requestCompleted方法執(zhí)行調(diào)用者所指定的塊這段代碼需要把completion handler保存到實(shí)例變量里面负拟。

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject
@property (nonatomic, strong, readonly) NSURL *url;
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end


@interface EOCNetworkFetcher()

@property (nonatomic, strong, readwrite) NSURL *url;
@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic, strong) NSData *downloadData;

@end

@implementation EOCNetworkFetcher

- (instancetype)initWithURL:(NSURL *)url
{
    if (self = [super init]) {
        _url = url;
    }
    return self;
}

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion
{
    self.completionHandler = completion;
    //Start the request
    //Request sets downloadedData property
    //When request is finished, p_requestCompleted is called
}

- (void)p_requestCompleted
    {
    if (_completionHandler) {
        _completionHandler(_downloadData);
    }
}

某個(gè)類(lèi)可能會(huì)創(chuàng)建這種網(wǎng)絡(luò)數(shù)據(jù)獲取器對(duì)象,并用其從URL中下載數(shù)據(jù):

@implementation ViewController
{
    EOCNetworkFetcher *_networkFetcher;
    NSData *_fetchedData;
}

- (void)downloadData
{
    NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

    [_networkFetcher startWithCompletionHandler:^(NSData *data) {
        _fetchedData = data;
    }];
}

因?yàn)閏ompletion handler塊要設(shè)置_fetchedData實(shí)例變量歹河,所以它必須捕獲self變量(變量捕獲詳見(jiàn)第37條)掩浙。這就是說(shuō)花吟,handler塊保留了創(chuàng)建網(wǎng)絡(luò)數(shù)據(jù)獲取器的那個(gè)EOCClass實(shí)例。而EOCClass實(shí)例則通過(guò)strong實(shí)例變量保留了獲取器厨姚,最后衅澈,獲取器對(duì)象又保留了handler塊。

要打破保留環(huán)也很容易:要么令_netwrokFetcher實(shí)例變量不再引用獲取器谬墙,要么令獲取器的completionHandler屬性不再持有handler塊今布。在網(wǎng)絡(luò)數(shù)據(jù)獲取器這個(gè)例子中,應(yīng)該等completion handler塊執(zhí)行完畢之后拭抬,再去打破保留環(huán)部默,以便使獲取器對(duì)象在handler塊執(zhí)行期間保持存活狀態(tài)。比方說(shuō)造虎,completion handler塊的代碼可以這么修改:

[_networkFetcher startWithCompletionHandler:^(NSData *data) {
    _fetchedData = data;
    _networkFetcher = nil;
}];
網(wǎng)絡(luò)數(shù)據(jù)獲取器和擁有它的EOCClass類(lèi)實(shí)例之間構(gòu)成了保留環(huán)

在本例中傅蹂,唯有completion handler運(yùn)行過(guò)后,方能解除保留環(huán)算凿。若是completion handler一直不運(yùn)行份蝴,那么保留環(huán)就無(wú)法打破,于是內(nèi)存就會(huì)泄露氓轰。

像completion handler塊這種寫(xiě)法婚夫,還可能引入另外一種形式的保留環(huán)。如果completion handler塊所引用的對(duì)象最終又引用了這個(gè)塊本身署鸡,那么就會(huì)出現(xiàn)保留環(huán)案糙。

- (void)downloadData
{
    NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
    EOCNetworkFetcher *networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

    [networkFetcher startWithCompletionHandler:^(NSData *data) {
        _fetchedData = data;
    }];
}

而這次比剛才那個(gè)例子更難于發(fā)覺(jué),completion handler塊其實(shí)要通過(guò)獲取器對(duì)象來(lái)引用其中的URL靴庆。于是时捌,塊就要保留獲取器,而獲取器反過(guò)來(lái)又經(jīng)由其completionHandler屬性保留了這個(gè)塊撒穷∠灰回想一下,獲取器對(duì)象之所以要把completionHandler塊保存在屬性里面端礼,其唯一目的就是想稍后使用這個(gè)塊禽笑。可是蛤奥,獲取器一旦運(yùn)行過(guò)completion handler之后佳镜,就沒(méi)有必要在保留它了。

- (void)p_requestCompleted
{
    if (_completionHandler) {
        _completionHandler(_downloadData);
        self.completionHandler = nil;
    }
}

這樣一來(lái)凡桥,只要下載請(qǐng)求執(zhí)行完畢蟀伸,保留環(huán)就解除了,而獲取器對(duì)象也將會(huì)在必要時(shí)為系統(tǒng)所回收。請(qǐng)注意啊掏,之所以把completion handler暴露為獲取對(duì)象的公共屬性蠢络,那么就不便在執(zhí)行完下載請(qǐng)求之后直接將其清理掉了,因?yàn)榧热灰呀?jīng)把handler作為屬性公布了迟蜜,那就意味著調(diào)用者可以自由使用它刹孔,若是此時(shí)有在內(nèi)部將其清理掉的話(huà),則會(huì)破壞“封裝語(yǔ)義”(encapsulation semantic)娜睛。在這種情況下要想打破保留環(huán)髓霞,只有一個(gè)辦法可用,那就是強(qiáng)迫調(diào)用者在handler代碼里自己把completionHandler屬性清理干凈畦戒》娇猓可這并不十分合理,因?yàn)槟銦o(wú)法假定調(diào)用者一定會(huì)這么做障斋,他們反過(guò)來(lái)會(huì)抱怨你沒(méi)把內(nèi)存泄漏問(wèn)題處理好纵潦。

要點(diǎn)

  • 如果塊所捕獲的對(duì)象直接或間接地保留了塊本身,那么就得當(dāng)心保留環(huán)問(wèn)題配喳。
  • 一定要找個(gè)適當(dāng)?shù)臅r(shí)機(jī)解除保留環(huán)酪穿,而不能把責(zé)任推給API的調(diào)用者凳干。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晴裹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子救赐,更是在濱河造成了極大的恐慌涧团,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件经磅,死亡現(xiàn)場(chǎng)離奇詭異泌绣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)预厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)阿迈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人轧叽,你說(shuō)我怎么就攤上這事苗沧。” “怎么了炭晒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵待逞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我网严,道長(zhǎng)识樱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮怜庸,結(jié)果婚禮上当犯,老公的妹妹穿的比我還像新娘。我一直安慰自己割疾,他們只是感情好灶壶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著杈曲,像睡著了一般驰凛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上担扑,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天恰响,我揣著相機(jī)與錄音,去河邊找鬼涌献。 笑死胚宦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的燕垃。 我是一名探鬼主播枢劝,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼卜壕!你這毒婦竟也來(lái)了您旁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤轴捎,失蹤者是張志新(化名)和其女友劉穎鹤盒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體侦副,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侦锯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秦驯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尺碰。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖译隘,靈堂內(nèi)的尸體忽然破棺而出亲桥,到底是詐尸還是另有隱情,我是刑警寧澤细燎,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布两曼,位于F島的核電站,受9級(jí)特大地震影響玻驻,放射性物質(zhì)發(fā)生泄漏悼凑。R本人自食惡果不足惜偿枕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望户辫。 院中可真熱鬧渐夸,春花似錦、人聲如沸渔欢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奥额。三九已至苫幢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垫挨,已是汗流浹背韩肝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留九榔,地道東北人哀峻。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哲泊,于是被迫代替她去往敵國(guó)和親剩蟀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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