在后台线程中使用时 NSTimer 导致内存泄漏

NSTimer leading memory leak when using in background thread

- (void)addTimer {
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        __strong typeof(self) strongSelf = weakSelf;
        strongSelf.timer = [NSTimer scheduledTimerWithTimeInterval:20 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
            }];
            NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
            [currentLoop addTimer:strongSelf.timer forMode:NSRunLoopCommonModes];
            [currentLoop run];
        
    });
}

为什么这段代码导致内存泄漏?当我删除 __strong typeof(self) strongSelf = weakSelf; 时,它起作用 fine.Should 我添加 __strong typeof(self) strongSelf = weakSelf;?

- (void)addTimer {
    __weak __typeof(self)weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:20 repeats:YES block:^(NSTimer * _Nonnull timer) {
            }];
            NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
            [currentLoop addTimer:weakSelf.timer forMode:NSRunLoopCommonModes];
            [currentLoop run];
    });
}

这段代码可以工作。在块中,您需要对象的弱引用。如果你在一个块中占据了一个对象的据点,那么 ARC 将无法释放它的内存。

NSTimer 完全不同的解决相同问题的方法。 您需要一个免费的 运行 非中断计时器,该计时器每 20 秒触发一次并且不应泄漏,即使目标已解除分配也是如此。这是 NSProxy 派上用场的一种情况。这只是多了三行代码,但安全。

假设您有一个 NSObject,其中包含您要传递调用的“updateMethod”。

@interface YourTargetObject : NSObject
-(void)updateMethod;
@end

NSProxy 看起来像。

@interface YourWeakDelegateProxy : NSProxy
-(instancetype)initWithTarget:(YourTargetObject*)target;
@property (nonatomic, weak) YourTargetObjectClass * target;
@end

详细调用。我们在这里只传递一个委托指针并将调用转发给选择器。

@implementation YourWeakDelegateProxy
-(instancetype)initWithTarget:(YourTargetObject*)target {
    self.target = target;
    return self;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [_target methodSignatureForSelector:selector];
}
-(void)forwardInvocation:(NSInvocation *)invocation {
    [invocation setTarget:_target];
    [invocation invoke];
}
@end

现在您可以通过以下方式在 class 中的某处使用计时器调用对象(可能以后不再存在)..

self.timer = [NSTimer 
    scheduledTimerWithTimeInterval:20.0 
    target:[[YourWeakDelegateProxy alloc] initWithTarget:self]
    selector:@selector(updateMethod) 
    userInfo:nil 
    repeats:NO];

并且像往常一样关注计时器的重新分配..
实际上,ARC 会为您做到这一点......但它仍然看起来像..

-(void)dealloc {
    [_timer invalidate];
    _timer = nil;
}

所以现在计时器可以保证在新代理上调用。
代理转发它对委托的调用。如果委托(自己)在您的 20 秒之间变得无效,则不会发生任何坏事。

PS:提醒..每个线程至少有一个runloop,但不是每个runloop都有自己的线程,而是有时它们共享一个线程。