0
- (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];
        
    });
}

Why this code leading memory leak? When i delete __strong typeof(self) strongSelf = weakSelf;,it work fine.Should i add __strong typeof(self) strongSelf = weakSelf;?

2 Answers2

1

A completely different approach to the same problem with NSTimer. You want a free running non-interupted timer that will fire each 20seconds and should not leak, even if the target is deallocated. That is one of the cases when a NSProxy comes into play very handy. It's just three more lines of code but safe.

Let's assume you have an NSObject with an "updateMethod" you want to pass the invocation on.

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

The NSProxy would look like.

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

The invocation in detail. We only pass a delegates pointer here and forward the invocation to the selector.

@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

now you can invoke a (possibly later not anymore existing) object with your timer somewhere in your class in the following way..

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

and as usually keeping an eye on the timers deallocation..
Actually ARC does that for you.. but still it would look like..

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

So now the timer is guaranteed to invoke on a new proxy.
The proxy forwards its invocation on the delegate. If the delegate (self) becomes invalid in between your 20 seconds nothing bad happens.

PS: reminder.. Each thread has at least one runloop but not each runloop does have its own thread, instead they sometime share one thread.

Ol Sen
  • 3,163
  • 2
  • 21
  • 30
  • https://stackoverflow.com/questions/27500802/how-to-remove-timer-from-runloop-immediately Thank you very much, now i know what happen in this case, you can read this answer, we musk stop runloop and invalidate timer. – SunJunXiang Feb 26 '21 at 03:37
  • In general.. when you need to nest a NSTimer inside dispatching something un-optimal is going on because dispatching has also a timing component that you could use instead. – Ol Sen Feb 26 '21 at 08:25
  • Your mean dispatch_source_t? I know this GCD timer, and it's very powerful, but i want to know what happen with NSTimer work in background thread. So i write this question in stackoverflow, now i know why above case leading memory leak, because if runloop unable terminal, this thread will not exit, and this thread strong retain 'self'.It's very kind of you to answer my question, thanks. – SunJunXiang Feb 26 '21 at 09:39
0
- (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];
    });
}

This code will work. In the blocks, you need a weak reference of an Object. If you take a stronghold of an object inside a block then ARC will be unable to release its memory.

  • But we normally use weak/strong dance to fix memory leaks, why problem happen in this situation? – SunJunXiang Feb 24 '21 at 10:37
  • In this case. you should declare it as weak because inside a blocks capture the stronghold of self. If you don't use weak it will never go off memory by ARC. It's a common practice that inside any closure or blocks we must use weak or unowned objects as per need. And Always try to ignore the strong reference. Otherwise, it will create a leak. – Ashish Kr Singh Feb 24 '21 at 10:41
  • Thanks, you can try this code, - (void)addTimer { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_async(dispatch_get_main_queue() ^{ __strong typeof(self) strongSelf = weakSelf; strongSelf.timer = [NSTimer scheduledTimerWithTimeInterval:20 repeats:YES block:^(NSTimer * _Nonnull timer) { [strongSelf doSomething]; }]; [ [NSRunLoop currentRunLoop] addTimer:strongSelf.timer forMode:NSRunLoopCommonModes]; }); } this code work fine. So i guess problem is i create new thread, and start it's runloop. – SunJunXiang Feb 24 '21 at 10:53
  • Maybe thread never terminal, leading self never release? Thank you for answer my question. – SunJunXiang Feb 24 '21 at 10:57
  • @SunJunXiang Possibly better create an NSProxy subclass instead that will invoke the object you need and so will never fire if the object(its delegate) does not exist anymore. That way you can use `[NSTimer scheduledTimerWithTimeInterval:20 target:yourDelegate selector:@selector(updateMethod:) userInfo:@{@"somekey":@(value)} repeats:YES];` That way you also avoid messing with the timers thread. – Ol Sen Feb 24 '21 at 11:47
  • Think of an NSProxy as if you pass around only the delegates pointer instead of passing around the timer (which is running in its own runloop anyway). "updateMethod:" is the method inside the delegate object that will be fired when the time is up to repeat. So the target can be a subclass of NSProxy which you give the delegate and allocate this proxy once when setting up the timer. – Ol Sen Feb 24 '21 at 11:54