Reputation: 3582
I have a singleton class in my app that is created at app launch and always in use. I now am going to introduce an NSTimer that calls one of the singleton's methods periodically, so my understanding is that the timer will retain a strong reference to my singleton class (since the singleton class is the target). Is this correct? More importantly, is a strong retain cycle a problem for a singleton class that should live for the duration of the app? If so, why?
Upvotes: 2
Views: 318
Reputation: 437532
You ask:
I have a singleton class in my app that is created at app launch and always in use. I now am going to introduce an
NSTimer
that calls one of the singleton's methods periodically, so my understanding is that the timer will retain a strong reference to my singleton class (since the singleton class is the target). Is this correct?
Yes, NSTimer
will maintain a strong reference to that instance of the singleton class. This strong reference is maintained until you invalidate
the timer (or in the case of a non-repeating timer, until the timer fires).
More importantly, is a strong retain cycle a problem for a singleton class that should live for the duration of the app? If so, why?
The fact that a scheduled NSTimer
instance maintains a strong reference to the target of its handler is not a problem, per se. You just have to be aware of this fact and manage your memory appropriately. All this means is that if you're done with some object that is a target of the NSTimer
, that you are responsible for manually calling invalidate
of that timer.
The only time that this strong reference to the timer's handler becomes problematic is when you're dealing with some object that that is of a more transitory nature (e.g. a view controller that might later be dismissed). Too often people think "oh, I'll just invalidate
that NSTimer
in the dealloc
" method, but that doesn't work. The Objective-C dealloc
method (as well as the Swift deinit
method) is not called until there are no more strong references, so you can't try to resolve that timer's strong reference in dealloc
, because dealloc
can't get called until the timer is invalidated. Instead if you're done with the target of a NSTimer
, you must manually invalidate
the timer (e.g. in the case of the view controller example, people frequently do that in viewDidDisappear
).
In your case, the fact that you have a scheduled repeating timer, calling the singleton method is not a problem. Just be aware that this singleton object (which will likely never fall out of scope anyway) cannot be deallocated as long as the scheduled NSTimer
is still active. You'd have to invalidate
the NSTimer
in order to resolve the timer's strong reference to that target.
Two additional observations:
You've described this NSTimer
behavior as a strong reference cycle (retain cycle). Technically, this isn't a strong reference cycle, but rather just a fact that a schedule NSTimer
is retained by the run loop on which it's scheduled, and then the timer then retains the target upon which its handler is scheduled.
This is an somewhat academic distinction (because it manifests itself much like a strong reference cycle), but it's worth noting.
You should also be aware that instead of using a NSTimer
, you can also use a GCD dispatch source timer. So, create a timer property in the class:
@property (nonatomic, strong) dispatch_source_t timer;
And then instantiate and start the timer:
typeof(self) __weak weakSelf = self;
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.timer, ^{
[weakSelf handleTimer];
});
dispatch_resume(self.timer);
This pattern, unlike the NSTimer
approach, does not retain the target of the timer (or, in this case, the object upon which we're referencing via self
) because of the use of weakSelf
pattern in the block (which resolves any strong reference cycle). Thus we end up in a timer that is safely released when the object that contains it is deallocated, with no strong reference cycle.
Upvotes: 3
Reputation: 32786
The retain cycle might not be problematic at this time as you're using a singleton that doesn't need deallocation, but what if in the future someone else refactors the singleton into a regular class?
Another consideration is that NSTimer
's are not very energy efficient, and Apple warmly recommends switching to GCD
sources for performing timer-related tasks. You might even be able to break the retain cycle via weak
references with this approach.
Upvotes: 0
Reputation: 17958
I would consider it a problem. A singleton class is already hard to refactor and reuse. Adding a retain cycle to one is going to make the class that much harder to change or reuse in the future. When you discover that you need this class (or at least the set of responsibilities this timer is a part of) to not be a singleton you'll also need to remember or identify this memory leak and fix it as part of your refactor. Future you will probably be sad.
Instead you can write a class which follows good patterns (for memory management and otherwise) regardless of if it is accessed as a singleton or not. In your case that might mean keeping a weak reference to the timer ("Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.") and invalidating that timer if this object is deallocated.
Upvotes: 1