helloB
helloB

Reputation: 3582

Is a strong retain cycle a relevant consideration for a singleton class that should exist for the life of an app?

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

Answers (3)

Rob
Rob

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:

  1. 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.

  2. 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

Cristik
Cristik

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

Jonah
Jonah

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

Related Questions