在后台线程中使用时 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都有自己的线程,而是有时它们共享一个线程。
- (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都有自己的线程,而是有时它们共享一个线程。