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必定被調用,就極易形成內存泄露。oop
當指向XXClass實例的最後一個外部引用移走以後,該實例仍然會繼續存活,由於定時器還保留着它。而定時器對象也不可能被系統釋放,由於實例中還有一個強引用正在指向它。這種內存泄露是很嚴重的,若是定時器每次輪訓都執行一些下載工做,經常會更容易致使其餘內存泄露問題。atom
針對於此,有人想到利用block來避免這種循環應用。spa
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。.net
NSProxy自己是一個抽象類,它遵循NSObject協議,提供了消息轉發的通用接口。NSProxy一般用來實現消息轉發機制和惰性初始化資源。code
使用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
消息轉發涉及到三個核心方法blog
1 //消息轉發第一步,在這裏能夠動態的爲類添加方法,這樣類本身就能處理了 2 +resolveInstanceMethod: 3 //消息轉發第二步,在第一步沒法完成的狀況下執行。這裏只是把一個Selector簡單的轉發給另外一個對象 4 - forwardingTargetForSelector: 5 //消息轉發第三步,在第二步也沒法完成的狀況下執行。將整個消息封裝成NSInvocation,傳遞下去 6 - forwardInvocation:
消息轉發機制使得代碼變的很靈活:一個類自己能夠徹底不實現某些方法,它只要能轉發就能夠了。繼承
@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: