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: