Re: Circular references caused by scheduled NSTimers?
Re: Circular references caused by scheduled NSTimers?
- Subject: Re: Circular references caused by scheduled NSTimers?
- From: Greg Parker <email@hidden>
- Date: Thu, 18 Apr 2013 23:35:46 -0700
On Apr 18, 2013, at 11:01 PM, Oleg Krupnov <email@hidden> wrote:
> I recently encountered an alarming problem that I have never been
> aware of, and I'd like to ask about the best practice to handle it.
>
> Is it true that any object (let's call it Controller) that owns an
> NSTimer, scheduled to fire a method (let's call it -timerDidFire) of
> the same Controller, creates a circular reference and causes the
> Controller to never be released and the timer never stop triggering?
>
> It looks like scheduling the timer causes the run loop to retain a
> reference to the object that implements -timerDidFire, in this case
> it's the Controller. Is this true? Then this is a circular reference,
> right?
>
> In this case, if [_timer invalidate]; is called only in Controller's
> dealloc, then the timer never stops firing?
>
> Which is the best way to prevent this problem? Currently I'm thinking
> about some kind of -[controller disconnect]; method that I need to
> call before [controller release]; in controller's parent object, but
> that seems a bit unwieldy solution.
>
> Any other ideas/considerations? Thanks!
Yes, NSTimer retains its target. If the timer is a repeating one, then something other than the timer target's -dealloc method needs to invalidate the timer.
A -disconnect or -invalidate or responsible parent controller is one solution.
Another solution is to introduce a weak reference between the timer and your controller. Create a helper class that holds a weak reference to your Controller object. The helper object implements nothing except to respond to the timer method by calling the controller's method. Schedule the timer targeting the helper object.
Now the timer no longer retains the controller, so -[Controller dealloc] will run and it can invalidate the timer. When the timer is invalidated it will in turn release the helper object, which then ought to become deallocated because there's no need to retain it anywhere else.
If you use an ARC __weak variable instead of an ordinary non-retaining variable, it will also solve an otherwise thorny multithreading race. The bug occurs when the timer fires after -[Controller dealloc] has started on another thread but before -[Controller dealloc] is able to unregister the timer. This means the timer method is running on a partially-destroyed controller object, which is unlikely to work well. Using a __weak variable fixes it because the __weak variable becomes nil precisely when the controller object's retain count becomes zero. Either the helper object sends the message to a valid and strongly-referenced Controller, or the helper object sends a message to nil which does nothing. The controller's timer method and -dealloc method won't interfere with each other.
--
Greg Parker email@hidden Runtime Wrangler
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden