今天看文章發(fā)現(xiàn)一片關(guān)于
Retain Cycle
的老生常談的問題,但是作者從開發(fā)常見場景的代理和Block
分析了原因,分析的不錯雹锣,加深了理解网沾,索性小譯一下,加上了一些自己的圖解蕊爵,分享出來辉哥。歡迎轉(zhuǎn)載評論,注明原文地址即可~
Avoid Strong Reference Cycles
隨著
ARC
的引入攒射,內(nèi)存管理變得更容易了醋旦。然而,即使您不必?fù)?dān)心何時保留和釋放会放,但仍然有一些規(guī)則需要您知道饲齐,以避免內(nèi)存問題。在這篇文章中咧最,我們將討論強引用循環(huán)捂人。
什么是一個強引用循環(huán)?假設(shè)你有兩個對象矢沿,對象A和對象B滥搭。如果對象A于對象B持有強引用,對象B于對象A有強引用捣鲸,那么就形成了一個強引用循環(huán)瑟匆。我們將討論兩種非常常見,需要注意循環(huán)引用的場景:Block和Delegate栽惶。
A->B: strong reference
B->A: strong reference
1. delegate
委托是OC中常用的模式愁溜。在這種情況下疾嗅,一個對象代表另一個對象或與另一個對象協(xié)調(diào)。委派對象保留對另一個對象(委托)的引用冕象,并在適當(dāng)?shù)臅r候向其發(fā)送消息代承。委托可以通過更新應(yīng)用程序的外觀或狀態(tài)來響應(yīng)。
(蘋果的)API
的一個典型例子是UITableView
及其Delegate
交惯。在本例中次泽,UITableView
對其Delegate
有一個引用,Delegate
有一個返回UITableView
的引用席爽,按照規(guī)則,每一個都是(指向?qū)Ψ剑┌∑3謱Ψ交钪欢停约词箾]有其他對象指向Delegate
或UITableView
,內(nèi)存也不會被釋放紫谷。(所以需要弱引用)
#import <Foundation/Foundation.h>
@class ClassA;
@protocol ClassADelegate <NSObject>
-(void)classA:(ClassA *)classAObject didSomething:(NSString *)something;
@end
@interface ClassA : NSObject
@property (nonatomic, strong) id<ClassADelegate> delegate;
這將在ARC
世界中生成一個保留循環(huán)齐饮。為了防止這一點,我們需要做的只是將對委托的引用更改為弱引用~
@property (nonatomic, weak) id<ClassADelegate> delegate;
弱引用并未實現(xiàn)對象間的擁有權(quán)或職責(zé)笤昨,并不能使一個對象存活在內(nèi)存中祖驱。如果沒有其他對象指向delegate
代理或者委托對象,那么delegate
代理將被釋放瞒窒,隨之delegate
代理釋放對委托對象的強引用捺僻。如果沒有其他對象指向委托對象,則委托對象也將被釋放崇裁。
2. Blocks
塊
Block
是類似于C函數(shù)的代碼塊匕坯,但除了可執(zhí)行代碼外,它們還可能包含堆棧中的變量拔稳。因此葛峻,Block
可以維護一組數(shù)據(jù),用于在執(zhí)行時影響行為采记。因為Block
保持代碼的執(zhí)行所需要的數(shù)據(jù),他們是非常有用的回調(diào)挺庞。
官方文檔:
Block
是Objective-C
對象援制,但是有些內(nèi)存管理規(guī)則只適用于Block
褐墅,而非其他Objective-C
對象。
Block
內(nèi)對任何所捕獲對象的保持強引用,包括Block
自身艘款,因此Block
很容易引起強引用循環(huán)益眉。如果一個類有這樣一個Block
的屬性:
@property (copy) void (^block)(void);
在它的實現(xiàn)中,你有一個這樣的方法:
- (void)methodA {
self.block = ^{
[self methodB];
};
}
self->block: strong reference
block->self: strong reference
然后你就得到了一個強引用循環(huán):對象self
對block
有強引用,而block
正好持有一個self
的強引用傲诵。
Note: For block properties its a good practice to use copy, because a block needs to be copied to keep track of its captured state outside of the original scope.
注意:關(guān)于block
的屬性設(shè)置剧罩,使用copy
是一個很好的方式挑势,因為block
需要被復(fù)制后用以在原始作用域外來捕獲狀態(tài)。
為了避免這種強引用循環(huán),我們需要再次使用弱引用。下面就是代碼的樣子:
- (void)methodA {
ClassB * __weak weakSelf = self;
self.block = ^{
[weakSelf methodB];
};
}
通過捕獲對自身的弱引用,block
不會保持與對象的強引用。如果對象被釋放之前的block
稱為weakself
指針將被設(shè)置為nil
。雖然這很好,因為不會出現(xiàn)內(nèi)存問題,如果指針為nil
禾蚕,那么block
內(nèi)的方法就不會被調(diào)用倍试,所以block
不會有預(yù)期的行為。為了避免這種情況,我們將進一步修改我們的示例:
- (void)methodA {
__weak ClassB *weakSelf = self;
self.block = ^{
__strong ClassB *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf methodB];
}
};
}
我們在block
內(nèi)部創(chuàng)建一個Self
對象的強引用攘已。此引用將屬于block
炮赦,只要block
還在,它將存活內(nèi)存中。這不會阻止Self
對象被釋放峡眶,我們?nèi)匀豢梢员苊鈴娨醚h(huán)辫樱。
并不是所有的強引用循環(huán)都很容易看到拣展,正如示例中的那樣,當(dāng)您的塊代碼變得更復(fù)雜時敦冬,您可能需要考慮使用弱引用辅搬。
這是兩種常見的模式,它們可以出現(xiàn)強引用循環(huán)夯缺。正如您所看到的蚤氏,只要您能夠正確地識別它們,就很容易用弱引用來破壞這些循環(huán)踊兜。即便ARC
讓我們更容易管理內(nèi)存竿滨,但是你仍需要注意。
附注:翻譯中,為了靠近原文意思于游,
強引用循環(huán)
就是大家經(jīng)常說的循環(huán)引用毁葱。
附:Block的一點碎碎念
-
block
要用copy
修飾,還是用strong
?
NSString贰剥、NSArray倾剿、NSDictionary 等等經(jīng)常使用copy關(guān)鍵字蚌成,是因為他們有對應(yīng)的可變類型:NSMutableString前痘、NSMutableArray、NSMutableDictionary担忧;
block 也經(jīng)常使用 copy 關(guān)鍵字芹缔,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks:
block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅瓶盛,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作最欠。如果不寫 copy ,該類的調(diào)用者有可能會忘記或者根本不知道編譯器會自動對 block 進行了 copy 操作
惩猫,他們有可能會在調(diào)用之前自行拷貝屬性值芝硬。這種操作多余而低效。你也許會感覺我這種做法有些怪異轧房,不需要寫依然寫吵取。如果你這樣想,其實是你日用而不知
锯厢,