解决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必定被调用,就极易形成内存泄露。oop

当指向XXClass实例的最后一个外部引用移走以后,该实例仍然会继续存活,由于定时器还保留着它。而定时器对象也不可能被系统释放,由于实例中还有一个强引用正在指向它。这种内存泄露是很严重的,若是定时器每次轮训都执行一些下载工做,经常会更容易致使其余内存泄露问题。atom

针对于此,有人想到利用block来避免这种循环应用。spa

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。.net

weakProxy解决循环引用

NSProxy

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:

消息转发机制使得代码变的很灵活:一个类自己能够彻底不实现某些方法,它只要能转发就能够了。继承

 

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与消息转发机制

相关文章
相关标签/搜索