NSTimer 循环引用问题

题记

在iOS 10系统以前,系统的NSTimer是会引发循环引用的,致使内存泄漏。下面就针对这个问题给出几种解决方法。ios

在iOS 10之后系统,苹果针对NSTimer进行了优化,使用Block回调方式,解决了循环引用问题。git

//API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
       // Do some things
    }];
复制代码

这个iOS 10方法能解决循环引用问题。可是咱们目前大部分仍是要适配iOS10如下系统。github

平时咱们都是这么使用NSTimermacos

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];

    //或者
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode];

- (void)testTimer {
    NSLog(@"Do Some Things");
}

//中止Timer
- (void)dealloc {
    [self.myTimer invalidate];
    self.myTimer = nil;
}

复制代码
可是上面你有没有发现, 这个dealloc 方法根本不会调用,只要程序没杀死,就不会执行。造成了大的死循环。由于self.timer  与 NSTimer 的target:self(VC) 相互强引用,没有释放,怎么可能执行dealloc。

五种方式解决NSTimer循环引用问题

方法一:

使用(void)didMoveToParentViewController:(UIViewController *)parent方法,在这个方法里清掉定时器,就会释放Timer对象,也就解决了强引用。即调用dealloc方法了。bash

注意:针对PresentVC 不适用。oop

//生命周期  移除childVC的时候
- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
        [self.myTimer invalidate];
        self.myTimer = nil;
    }
}
复制代码

运行结果

方法二 中间件方法

消息传递没有什么是中间件不能解决的,若是有,那就在加中间件 O(∩_∩)O哈哈~优化

//定义个中间件属性
@property (nonatomic, strong) id target;

    _target = [NSObject new];
    class_addMethod([_target class], @selector(testTimer), (IMP)timerIMP, "v@:");
    //这里换成_target  不用self了。  这就没有了循环引用了
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(testTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode];

void timerIMP(id self, SEL _cmd) {
    NSLog(@"Do some things");
}

//中止Timer
- (void)dealloc {
    [self.myTimer invalidate];
    self.myTimer = nil;

    NSLog(@"Timer dealloc");
}
复制代码

此时也是能够的。ui

中间件方法

方法三:使用NSProxy类

新建一个类TimerProxy,继承NSProxy。atom

设置一个属性spa

//注意这里要使用weak
@property (nonatomic, weak) id target;
复制代码

而后在实现两个方法:

/** 方法签名 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
/** 消息转发 */
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

在你须要的地方,而后导入TimerProxy头文件使用
@property (nonatomic, strong) TimerProxy *timerProxy;
    _timerProxy = [TimerProxy alloc];//注意这里只有alloc方法
    _timerProxy.target = self;
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_timerProxy selector:@selector(testTimer) userInfo:nil repeats:YES];

复制代码

这时也能够解决循环引用问题。

方法四 仿照系统iOS 10 Block方法

新建一个NSTimer分类, QLTimer. 定义一个加方法

/** 定义一个加方法 */
+ (NSTimer *)QLscheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))timerBlock;

//实现方法
+(NSTimer *)QLscheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(nonnull void (^)(void))timerBlock {
    return [self scheduledTimerWithTimeInterval:timeInterval
                                         target:self
                                       selector:@selector(QLTimerHandle:)
                                       userInfo:[timerBlock copy] //注意copy
                                        repeats:repeats];
}

+(void)QLTimerHandle:(NSTimer *)timer {
    void(^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

使用
    __block typeof(self) weakSelf = self;
    self.myTimer = [NSTimer QLscheduledTimerWithTimeInterval:1.0 repeats:YES block:^{
        __block typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf testTimer];
    }];
复制代码

方法五

听说是通过苹果官方确认过的方法:used with GCD queues.

MSWeakTimer

以上方法均可以解决NSTimer 循环引用问题。暂时就这么多,其余方法但愿留言补充。谢谢!
相关文章
相关标签/搜索