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: