【OC梳理】循環(huán)引用及解決

什么是循環(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的引用,于是整個三個淡藍色的就都被釋放掉了。

參考文章:NSProxy與消息轉(zhuǎn)發(fā)機制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容