RetainCount OK to use in this instance?
Asked Answered
R

2

4

RetainCount == BAD

retainCount is taboo, unreliable, unpredictable, and in general shouldn't be used. I don't use it anywhere in my code, but I have seen it in one class that I use in an interesting way.

I have a class that runs a thread that runs indefinitely until the thread is cancelled. The catch is that the thread increases the retain count of the owner, in my case the class that instantiated it. So, even if I am done using that class, that instance is still going to hang around unless whoever is managing my class also has the smarts to know to shut down the thread. That is one solution, but this is what I found in code.

- (oneway void)release
{
    // This override allows allows this object to be dealloced 
    // by shutting down the thread when the thread holds the last reference.
    // Otherwise, the object will never be dealloc'd
    if (self.retainCount == 2)
    {
        [self quitDispatchThread];
    }

    [super release];
}

This is a clever solution, but I'm not sure what to think of it. It overrides release on the class and checks to see if the retain count is 2. In other words, it checks to see if the thread is the only thing keeping my object alive (since the retain count is about to be decremented from 2 to 1) and if it is, it terminates the thread (quitDispatchThread will block until the thread is terminated).

So...

Can you rely on retainCount to see if it is one?

Usually people say to stay clear of retainCount because you don't know if there are some autoreleases in there. However, if the retainCount is one then I know for a fact that only the thread is keeping it alive and I don't have to be concerned that the retainCount might be off due to some autoreleases, etc...

What's wrong with this code?

I was about to remove it, but it actually seems to make sense. Other objects don't have to have an awareness that my class is running a thread. Other objects can safely retain and release or even autorelease the object owning the thread without having to worry about shutting the thread down because it takes care of itself.

This code actually feels clean, which surprises me.

Edit :: NSThread is Retaining my Object

The retain count of my object is increased by the fact I'm using NSThread. My object is the target and the selector is the method the thread runs on.

initWithTarget:selector:object:

Returns an NSThread object initialized with the given arguments.

  • (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument

Parameters

target

The object to which the message specified by selector is sent.

selector

The selector for the message to send to target. This selector must take only one argument and must not have a return value.

argument

The single argument passed to the target. May be nil.

Return Value

An NSThread object initialized with the given arguments.

Discussion

For non garbage-collected applications, the method selector is responsible for setting up an autorelease pool for the newly detached thread and freeing that pool before it exits. Garbage-collected applications do not need to create an autorelease pool.

The objects target and argument are retained during the execution of the detached thread. They are released when the thread finally exits.

Refer answered 5/4, 2012 at 16:3 Comment(4)
(tongue in cheek) If you think this code is so clean, try porting it to ARC.Gymnosperm
Trying to fix any problem in Cocoa by manipulating retainCount is a signal that there is a design problem. I think circular ownership is the root of the problem. Can you redesign your code so that the thread doesn't have a retained reference to the owning object? This code seems clean in the way a waxed mustache seems clean, but this code seems out of place like a waxed mustache is out of place on Grandma's lip.Temperamental
@Mr.Berna I hope grandma doesn't have a mustache, but if she did I'd hope it'd be waxed. :D I'm using NSThread which retains the target when you init it to a target, selector, argument. So... I don't think I can get around the circular ownership. However, there is exactly one place where this object (and its associated thread) get cleaned up as it is only used in one place in the code. I already have code in place to do this. I was even about to check in the change. However, as I reviewed the change, I had to ask myself why I was doing it, especially when I know it works.Refer
My answer has been updated; the last two paragraphs address your use of -[NSThread initWithTarget:selector:object:]. In short, make the target something other than the thread's owner.Kirtle
K
6

retainCount is taboo, unreliable, unpredictable, and in general shouldn't be used.

You can rely on the value of retainCount IFF your objects don't pass through any code that's opaque to you, such as any Cocoa framework. In practice, this is almost impossible to achieve, thus the warning. The internals of Cocoa may be passing your object around, retaining, releasing, and putting it into autorelease pools many times and for many reasons, and you can't rely on its absolute value at any given point.

The catch is that the thread increases the retain count of the owner, in my case the class that instantiated it.

That's a retain cycle. The answer here is to find a way to break that cycle, not to subvert the reference counting mechanism. There must be some point before deallocation when either your thread or the owning object knows that the work the thread is performing is done (or needs to stop prematurely).

It sounds like the owning object is the interface for client code to the work the thread is doing. This owning object needs a "shutdown now" method that needs to be called (and documented as "must be called") before its owner releases it. In that shutdown method, you can break the cycle by releasing the thread.

I'm not exactly sure what's going on that the thread is retaining its creator (the cycle is a pretty clear indication that something is wrong with your ownership model) -- I'd guess you're using NSThread and initWithTarget:..., with the target being the creating/owning object. This is a bit of a mixup of the standard MVC pattern -- the owner of the thread is a "controller", the thread itself (and the code it runs) more of a "model".

The controller should not contain the thread's code, in other words. I'd recommend that you factor out the thread's code into another object to use as the target. Then the controller object owns both the thread itself and the "work" target object, with neither of them owning the controller. Voilà, no cycle!

Kirtle answered 5/4, 2012 at 16:47 Comment(0)
C
2

Nope. You're relying on an abstracted implementation which publicly bears the sign "Keep Out" on its gate.

Just manage your dependencies using your own memory and implementation. This might include an ivar and/or methods for completion/destruction phases.

See also the documentation for release:

release

Decrements the receiver’s reference count. (required)

- (oneway void)release

Discussion

The receiver is sent a dealloc message when its reference count reaches 0.

You would only implement this method to define your own reference-counting scheme. Such implementations should not invoke the inherited method; that is, they should not include a release message to super.

If you want to be sure your program behaves correctly across multiple releases, it's best to change it now.

Czarism answered 5/4, 2012 at 17:9 Comment(2)
+1 I like how the docs for release specifically say not to call the super. That alone makes the release override a bad idea. Thanks for the answer.Refer
@Refer yeah, i figure that means 'reserved for optimization'. idk of current optimizations which affect the behavior, but I don't go down this road. you've probably tested it as much as i have at this point :) you're welcome.Czarism

© 2022 - 2024 — McMap. All rights reserved.