通過(guò)WeakProxy防止循環(huán)引用

今天看FLAnimatedImage時(shí)無(wú)意只發(fā)現(xiàn)一個(gè)proxy類(lèi)FLWeakProxy,里面有這樣的代碼:

  [self.weakProxy performSelector:@selector(resetFrameCacheSizeMaxInternal) withObject:nil afterDelay:kResetDelay];

因?yàn)閜erformSelector : afterDelay會(huì)強(qiáng)引用當(dāng)前類(lèi),為了防止內(nèi)存泄露才用的proxy。

1 .先貼出代碼 FLWeakProxy.h文件:

@interface FLWeakProxy : NSProxy

+ (instancetype)weakProxyForObject:(id)targetObject;

@end

2.FLWeakProxy.m文件

#import "FLWeakProxy.h"

@interface FLWeakProxy ()

@property (nonatomic, weak) id target;

@end


@implementation FLWeakProxy

#pragma mark Life Cycle

// This is the designated creation method of an `FLWeakProxy` and
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
+ (instancetype)weakProxyForObject:(id)targetObject
{
    FLWeakProxy *weakProxy = [FLWeakProxy alloc];
    weakProxy.target = targetObject;
    return weakProxy;
}


#pragma mark Forwarding Messages

- (id)forwardingTargetForSelector:(SEL)selector
{
    // Keep it lightweight: access the ivar directly
    return _target;
}


#pragma mark - NSWeakProxy Method Overrides
#pragma mark Handling Unimplemented Methods

- (void)forwardInvocation:(NSInvocation *)invocation
{
    // Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
    // The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
    // We can't really handle struct return types here because we don't know the length.
    void *nullPointer = NULL;
    [invocation setReturnValue:&nullPointer];
}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    // We only get here if `forwardingTargetForSelector:` returns nil.
    // In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
    // We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
    // Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
    // See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
@end
  • _target 是弱引用, 為了打破循環(huán)。然后它在forwardingTargetForSelector把所有方法調(diào)用傳給了_target。
  • 當(dāng)_target被釋放后,如果方法被調(diào)用那么肯定會(huì)報(bào)doesNotRecognizeSelector,所以要重寫(xiě)forwardInvocationmethodSignatureForSelector兩個(gè)方法,它們響應(yīng)了method,但是不會(huì)做任何處理是返回nil,只要不報(bào)錯(cuò)就行。

那么我們?nèi)绾问褂媚兀?/h3>

主要有兩個(gè)場(chǎng)景,定時(shí)器NSTimer和performSelector。

1.定時(shí)器NSTimer

    _weakProxy = [FLWeakProxy weakProxyForObject:self]; 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:_weakProxy selector:@selector(timeUpdate) userInfo:nil repeats:YES];

2.performSelector :delay (沒(méi)成功)

代碼如下:

    _weakProxy = [FLWeakProxy weakProxyForObject:self];
    [_weakProxy performSelector:@selector(delayMethod) withObject:nil afterDelay:4];

結(jié)果不起作用!我認(rèn)為:雖然是_weakProxy調(diào)用的方法,但是真正是_target調(diào)用的performSelector,所以runloop還是強(qiáng)引用了_target,沒(méi)有起到打破循環(huán)的目的!
解決方法:

  • 如果讓weakProxy繼承的NSProxy改成NSObject就可以完美解決這個(gè)問(wèn)題,因?yàn)镹SObject本身有performSelector方法,所以是_weakProxy調(diào)用的performSelector,等4秒之后調(diào)用delayMethod時(shí)才會(huì)調(diào)用_target執(zhí)行,這樣是沒(méi)有問(wèn)題的!

  • 但是我查了一下NSProxy和NSObject的區(qū)別,都說(shuō)NSProxy是最適合做代理的,而且用NSObject之后,NSObject的category里的方法是無(wú)法正常調(diào)用的。像下面的代碼:

NSLog(@"%@",[proxyA valueForKey:@"length"]);//NSProxy
NSLog(@"%@",[proxyB valueForKey:@"length"]);//NSObject

結(jié)果是不一樣的,因?yàn)関alueForKey是catergory里的方法,無(wú)法被調(diào)用。

結(jié)語(yǔ): 如果只針對(duì)NSTimer時(shí),用NSProxy是沒(méi)有任何問(wèn)題的。但是對(duì)于performSelector不知如何是好,但是像FLAnimatedImage這么牛的第三方,按理說(shuō)不會(huì)出現(xiàn)這種嚴(yán)重的bug的。。。難道是我理解錯(cuò)了嗎?歡迎大家評(píng)論~~~~~~~~~

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

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

  • 首先介紹NSTimer的幾種創(chuàng)建方式 常用方法 三種方法的區(qū)別是: scheduledTimerWithTimeI...
    不吃雞爪閱讀 941評(píng)論 0 3
  • 設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類(lèi)型的...
    iOS菜鳥(niǎo)大大閱讀 809評(píng)論 0 1
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類(lèi)...
    司馬DE晴空閱讀 1,466評(píng)論 0 7
  • Shell 輸入/輸出重定向大多數(shù) UNIX 系統(tǒng)命令從你的終端接受輸入并將所產(chǎn)生的輸出發(fā)送回到您的終端。一個(gè)命令...
    海角hust閱讀 259評(píng)論 0 0
  • 時(shí)間過(guò)得很快,感覺(jué)在這里很久了,每天雖然做著同樣的工作,但是收獲卻不一樣每天都會(huì)遇到不同的人,驚喜無(wú)處不在。 這周...
    是派大星啊閱讀 4,258評(píng)論 0 1

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