解決NSTimer循環引用


NSTimer常見用法

 1 @interface XXClass : NSObject
 2 - (void)start;
 3 - (void)stop;
 4 @end
 5 
 6 @implementation XXClass {
 7     NSTimer *timer;
 8 }
 9 
10 - (id)init {
11     return [super init];
12 }
13 
14 - (void)dealloc {
15     [timer]
16 }
17 
18 - (void)stop {
19     [timer invalidate];
20     timer = nil;
21 }
22 
23 - (void)start {
24     timer = [NSTimerscheduledTimerWithTimeInterval:5.0 
25                                             target:self  
26                                           selector:selector(doSomething) 
27                                           userInfo:nil 
28                                            repeats:YES];
29 }
30 
31 - (void)doSomething {
32     //doSomething
33 }
34 
35 @end

創建定時器的時候,由於目標對象是self,所以要保留此實例。然而,因為定時器是用實例變量存放的,所以實例也保留了定時器,這就造成了循環引用。除非調用stop方法,或者系統回收實例,才能打破循環引用,如果無法確保stop一定被調用,就極易造成內存泄露。

當指向XXClass實例的最后一個外部引用移走之后,該實例仍然會繼續存活,因為定時器還保留着它。而定時器對象也不可能被系統釋放,因為實例中還有一個強引用正在指向它。這種內存泄露是很嚴重的,如果定時器每次輪訓都執行一些下載工作,常常會更容易導致其他內存泄露問題。

針對於此,有人想到利用block來避免這種循環應用。

Block解決循環引用

 1 @interface NSTimer (XXBlocksSupport)
 2 
 3 + (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
 4                                          block:(void(^)())block
 5                                        repeats:(BOOL)repeats;
 6 
 7 @end
 8 
 9 @implementation NSTimer (XXBlocksSupport)
10 
11 + (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
12                                          block:(void(^)())block
13                                        repeats:(BOOL)repeats
14 {
15     return [self scheduledTimerWithTimeInterval:interval
16                                           target:self
17                                         selector:@selector(xx_blockInvoke:)
18                                         userInfo:[block copy]
19                                          repeats:repeats];
20 }
21 
22 + (void)xx_blockInvoke:(NSTimer *)timer {
23     void (^block)() = timer.userinfo;
24     if(block) {
25         block();
26     }
27 }
28 
29 @end
30 //調用
31 - (void)start {
32     __weak XXClass *weakSelf = self;
33     timer = [NSTimer xx_scheduledTimerWithTimeInterval:.5
34                                                  block:^{
35                                                  XXClass *strongSelf = weakSelf;
36                                                  [strongSelf doSomething];
37                                                         }
38                                                repeats:YES];
39 }

定時器現在的target是NSTimer類對象,這是個單例,此處依然有類對象的循環引用.下面介紹更好的解決方式weakProxy。

weakProxy解決循環引用

NSProxy

NSProxy本身是一個抽象類,它遵循NSObject協議,提供了消息轉發的通用接口。NSProxy通常用來實現消息轉發機制和惰性初始化資源。

使用NSProxy,你需要寫一個子類繼承它,然后需要實現init以及消息轉發的相關方法。

1 //當一個消息轉發的動作NSInvocation到來的時候,在這里選擇把消息轉發給對應的實際處理對象
2 - (void)forwardInvocation:(NSInvocation *)anInvocation
3 
4 //當一個SEL到來的時候,在這里返回SEL對應的NSMethodSignature
5 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
6 
7 //是否響應一個SEL
8 + (BOOL)respondsToSelector:(SEL)aSelector

消息轉發機制

消息轉發涉及到三個核心方法

1 //消息轉發第一步,在這里可以動態的為類添加方法,這樣類自己就能處理了
2 +resolveInstanceMethod:
3 //消息轉發第二步,在第一步無法完成的情況下執行。這里只是把一個Selector簡單的轉發給另一個對象
4 - forwardingTargetForSelector:
5 //消息轉發第三步,在第二步也無法完成的情況下執行。將整個消息封裝成NSInvocation,傳遞下去
6 - forwardInvocation:

消息轉發機制使得代碼變的很靈活:一個類本身可以完全不實現某些方法,它只要能轉發就可以了。

 

WeakProxy來實現弱引用

@interface WeakProxy : NSProxy
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end

@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
    _target = target;
    return self;
}
+ (instancetype)proxyWithTarget:(id)target{
    return [[self alloc] initWithTarget:target];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.target respondsToSelector:aSelector];
}
@end

外部創建Timer

  self.timer = [NSTimer timerWithTimeInterval:1
                                         target:[WeakProxy proxyWithTarget:self]
                                       selector:@selector(timerInvoked:)
                                       userInfo:nil
                                        repeats:YES];

原理如下:

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

 

Reference:

1.用Block解決NSTimer循環引用

2.NSProxy與消息轉發機制

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM