什么是循環(huán)引用
循環(huán)引用是iOS開發(fā)中經(jīng)常遇到的問題,它指的是兩個或多個對象通過相互之間的強引用,形成了一個保留環(huán),即使已經(jīng)沒有外部對象持有,也無法對其進行釋放操作,也無法釋放其占用的內(nèi)存空間(引用計數(shù)器始終大于0)。
舉個簡單的例子:
對象A持有對象B,對象B持有對象C,對象C持有對象A,這時候它們之間就形成了一個引用環(huán):

這時候如果有一個對象D,引用了對象A,那么由于ABC之間的循環(huán)引用,它們的引用計數(shù)器如下:

那么即使D釋放了對象A,A、B、C的引用計數(shù)器仍然都是1,它們都不會被釋放回收。
循環(huán)引用有什么危害
由于循環(huán)引用的存在,使得產(chǎn)生循環(huán)引用的對象始終占有內(nèi)存空間,過多的循環(huán)引用會導(dǎo)致程序的內(nèi)存占用不斷升高,最終導(dǎo)致程序Creach。
常見的產(chǎn)生循環(huán)引用的幾種情況
1.Delegate及其他類似的相互引用的情況
用weak而不是strong就能解決這個問題了:
@property (nonatomic, weak) id <DelegateProtocol> delegate;
2.Block
Block的循環(huán)引用,主要是發(fā)生在ViewController中持有了block,比如:
@property (nonatomic, copy) CallbackBlock callbackBlock;
同時在對callbackBlock進行賦值的時候又調(diào)用了ViewController的方法,比如:
self.callbackBlock = ^{
[self doSomething];
}];
就會發(fā)生循環(huán)引用,因為:ViewController->強引用了callback->強引用了ViewController,解決方法也很簡單:
__weak __typeof(self) weakSelf = self;
self.callbackBlock = ^{
[weakSelf doSomething];
}];
那是不是所有的block都會發(fā)生循環(huán)引用呢?其實不然,比如UIView的類方法Block動畫,NSArray等的 類的遍歷方法,都不會發(fā)生循環(huán)引用,因為當(dāng)前控制器一般不會強引用一個類。
3.NSTimer
NSTimer是一種很容易忽略的循環(huán)引用的情況。
因為timer會強引用self,而self又持有了timer,這就造成了循環(huán)引用。
那么能不能像Block那樣用一個weak指針解決呢?比如
__weak typeof(self) weakSelf = self;
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomeThing) userInfo:nil repeats:YES];
但是其實并沒有用,因為不管是weakSelf還是strongSelf,最終在NSTimer內(nèi)部都會重新生成一個新的指針指向self,這是一個強引用的指針,結(jié)果就會導(dǎo)致循環(huán)引用。
如何解決呢?用NSProxy就是一個狠簡便的方法
NSProxy -- 解決NSTimer循環(huán)引用的利器
NSProxy本身是一個抽象類,它遵循NSObject協(xié)議,提供了消息轉(zhuǎn)發(fā)的通用接口。NSProxy通常用來實現(xiàn)消息轉(zhuǎn)發(fā)機制和惰性初始化資源。
PS:NSProxy的作用遠不止這一個方面,如果想要更多的了解,這里有一些文章可以參考:
iOS開發(fā)--利用NSProxy實現(xiàn)消息轉(zhuǎn)發(fā)-模塊化的網(wǎng)絡(luò)接口層設(shè)計
用 NSProxy 實現(xiàn)面向切面編程
oc中少見的不繼承于NSObject 的類NSProxy
想要更多?自行百度~
解決NSTimer循環(huán)引用問題
使用NSProxy,你需要寫一個子類繼承它,然后需要實現(xiàn)init以及消息轉(zhuǎn)發(fā)的相關(guān)方法:
//WeakProxy.h
@interface WeakProxy : NSProxy
@property ( weak , nonatomic , readonly) id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
//WeakProxy.m
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return[[self alloc] initWithTarget:target];
}
//當(dāng)一個消息轉(zhuǎn)發(fā)的動作NSInvocation到來的時候,在這里選擇把消息轉(zhuǎn)發(fā)給對應(yīng)的實際處理對象
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
//當(dāng)一個SEL到來的時候,在這里返回SEL對應(yīng)的NSMethodSignature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return[self.target methodSignatureForSelector:aSelector];
}
//是否響應(yīng)一個SEL
- (BOOL)respondsToSelector:(SEL)aSelector{
return[self.target respondsToSelector:aSelector];
}
@end
創(chuàng)建NSTimer時,使用如下方法:
self.timer = [NSTimer timerWithTimeInterval:1
target:[WeakProxy proxyWithTarget:self]
selector:@selector(timerInvoked:)
userInfo:nil
repeats:YES];
原理如下:

把虛線處變成了弱引用。于是,Controller就可以被釋放掉,我們在Controller的dealloc中調(diào)用invalidate,就斷掉了Runloop對Timer的引用,于是整個三個淡藍色的就都被釋放掉了。