关于NSTimer循环引用及解决

1. NSTimer循环引用问题

这是一个老生老生常谈的一个问题,但是其中还是有一些细节问题我觉得值得去写篇文章记录一下,其实这个问题是一个很有意思的问题。

  • 首先提问1.NSTImer的循环引用是timer 的拥有者A和timer的target A 造成的么?即timer=>self=>timer
    事实上及时timer不做为A的属性,或者timer 的target 传入__ weak, A也不会释放。
    原因在于,其实造成这种循环引用的根本原因并不是如此,timer内部会强引用target,即使传进来的是weak类型,造成这个问题其实是:runLoop=>timer=>target

2.NSTimer循环引用解决办法

要阻断这种循环引用我们就需要阻断runloop对timer的引用或者timer对target的强引用,但是timer的内部实现我们无法控制,而且timer为了防止调用target的action造成崩溃,肯定会对target进行强引用。
1> runloop=>timer=>proxy-->target
什么意思呢?
我们在timer和target中间添加一个模型,让timer的target为它,它弱引用占有需要重复执行方法的对象。proxy 采用消息转发的技术,将消息转发给需要定时重复执行方法的对象。还是拿代码来看一下吧。

/// 类声明
@interface TimerProxy : NSObject

@property (nonatomic, weak) id target;

+ (instancetype)_timerProxyWithTarget:(id)target;

@end

/// 类实现
@implementation TimerProxy

+ (instancetype)timerProxyWithTarget:(id)target {
    TimerProxy *timerProxy = [[TimerProxy alloc] init];
    timerProxy.target = target;
    return timerProxy;
}

/// 消息转发
/// 消息转发第一步:询问是否本类动态的实现了此方法,我们可以动态的添加实现的方法
/// 第二步:询问转发给某个对象处理此方法
/// 第三步:通过方法签名,生成一个NSInvocation对象,执行方法
/// 我们主要用到了后两步,把方法转发给本类target去执行,如果target不存在,就会进入到消息转发的第三步,我们执行NSObject的 init方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

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

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

-(void)dealloc {
    NSLog(@"_TimerProxy 释放了");
}

@end

/// 应用
NSTimer *testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TimerProxy timerProxyWithTarget:self] selector:@selector(excuteSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:testTimer forMode:NSRunLoopCommonModes];
    _timer = testTimer;
/// 需要在释放的时候,移除timer,否则proxy和timer之间循环引用
- (void)dealloc {
    NSLog(@"TestTimerObj释放了");
    [_timer invalidate];
    _timer = nil;
}
  1. 采用block的方式,runloop=>timer=>timerSelf=>block
    添加分类方法,采用timer将自己作为target,执行自己的一个方法,block作为userInfo传递。
@interface NSTimer (TestWeakly)

+ (instancetype)weaklyTimerWithTimerInterval:(NSTimeInterval)timerInterval block:(void(^)(void))excuteBlock repeat:(BOOL)repeat;

@end


@implementation NSTimer (TestWeakly)
/// 添加一个可执行
+ (instancetype)weaklyTimerWithTimerInterval:(NSTimeInterval)timerInterval block:(void (^)(void))executeBlock repeat:(BOOL)repeat {
    return [self scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(weaklyBlockInvoke:) userInfo:[executeBlock copy] repeats:repeat];
}

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

@end

/// 应用
/// 注意executeBlock内部引用self采用__weak
 __weak typeof(self) weakself = self;
    NSTimer *testTimer = [NSTimer weaklyTimerWithTimerInterval:1 block:^{
        __strong typeof(weakself) strongself = weakself;
        [strongself excuteSomething];
    } repeat:YES];
    
    [[NSRunLoop currentRunLoop] addTimer:testTimer forMode:NSRunLoopCommonModes];
    _timer = testTimer;
/// 销毁时,需要停止销毁timer
- (void)dealloc {
    NSLog(@"TestTimerObj释放了");
    [_timer invalidate];
    _timer = nil;
}
  1. 采用GCD的timer
/// 注意block的循环引用,造成内存泄漏
 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        [weakself excuteSomething];
    });
    dispatch_resume(timer);
    _gcdTimer = timer;
/// 释放GCDtimer
- (void)dealloc {
    NSLog(@"TestTimerObj释放了");
    dispatch_source_cancel(_gcdTimer);
}

上边就是NSTimer,循环引用,造成内存泄漏的原因。以及三种解决办法

你可能感兴趣的:(关于NSTimer循环引用及解决)