NSTimer的循环引用问题

797 阅读1分钟

1、timer的释放问题

日常使用timer的过程中如果用到了下述方法声明可能会引起循环引用的问题

- (void)setupTimer {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

由于self强引用了timer,同时timer也强引用了self,所以循环引用造成 dealloc 方法根本不会执行。这个时候self和timer都不会释放,造成内存泄漏

2、解决方案

2.1、分类实现

这里借用YY大佬的实现展示

@implementation NSTimer (YYAdd)

+ (void)_yy_ExecBlock:(NSTimer *)timer {
    if ([timer userInfo]) {
        void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
        block(timer);
    }
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

@end

个人觉得之所以需要多这一步主要是因为下边两个方法都要iOS10以后才可以使用

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
2.2、中间件的实现

这种方法来自网络,本人并没有用在项目中,因为感觉多此一举,没有必要。
这种实现的思路是添加一个中间件,通过弱引用解决相互引用的问题。

@interface HXHWeakObject()

@property (weak, nonatomic) id weakObject;

@end

@implementation HXHWeakObject

- (instancetype)initWithWeakObject:(id)obj {
    _weakObject = obj;
    return self;
}

+ (instancetype)proxyWithWeakObject:(id)obj {
    return [[HXHWeakObject alloc] initWithWeakObject:obj];
}

/**
 * 消息转发,让_weakObject响应事件
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return _weakObject;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_weakObject respondsToSelector:aSelector];
}

具体使用

// target要设置成weakObj,实际响应事件的是self
HXHWeakObject *weakObj = [HXHWeakObject proxyWithWeakObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakObj selector:@selector(changeText) userInfo:nil repeats:YES];

一些其他问题

1、为什么用weak修饰NSTimer实例不行呢?
因为timer被加到runloop里面了