views:

319

answers:

1

I have a class derived from NSThread:

@interface FSEventMonitorThread : NSThread {
    FSEventStreamRef m_fseStreamRef;
    CFRunLoopRef m_runLoop;
}

- (id) initWithStream:
    (FSEventStreamRef)fseStreamRef;

- (void) dealloc;

- (void) main;

@end

@implementation FSEventMonitorThread

- (id) initWithStream:
    (FSEventStreamRef)fseStreamRef
{
    if ( self = [super init] )
        m_fseStreamRef = fseStreamRef;
    return self;
}

 - (void) dealloc
{
    CFRunLoopStop( m_runLoop );
    FSEventStreamStop( m_fseStreamRef );
    [super dealloc];
}

- (void) main
{
    m_runLoop = CFRunLoopGetCurrent();
    FSEventStreamScheduleWithRunLoop(
        m_fseStreamRef, m_runLoop, kCFRunLoopDefaultMode
    );
    FSEventStreamStart( m_fseStreamRef );
    CFRunLoopRun();
}

@end

Elsewhere (inside a C++ function), I create an instance:

m_thread = [[FSEventMonitorThread alloc] initWithStream:m_fseStreamRef];

My understanding is that the retain-count should now be 1. In another C++ function, I want to stop and deallocate the thread:

[m_thread release];

Yet the dealloc method is not called. If I instead do:

[m_thread release];
[m_thread release];

then dealloc is called which implies the retain-count was 2. But how did it get to be 2?

Note that the documentation for NSThread only mentions retaining when using detachNewThreadSelector:toTarget:withObject:.

+3  A: 

The framework itself keeps ownership of the thread. This is necessary so that the thread object doesn't go away while the main method is executing. If you want to stop a thread, you are doing it the wrong way. You must provide some sort of inter-thread communication to signal the thread's main method that it should stop whatever it is doing, clean up, and exit. Once that happens, relinquishing your ownership of the thread will cause the thread to dealloc. You should never simply over-release something to get it to "go away". If you are doing that, you are almost certainly not using the provided objects the way they are meant to be used, as in this case.

A very simple example to cancel your thread might be:

- (void)finishThread
{
  if( [NSThread currentThread] != self ) // dispatch this message to ourself
    [self performSelector:@selector(finishThread) onThread:self withObject:nil waitUntilDone:NO];
  else
    CFRunLoopStop(CFRunLoopGetCurrent());
}
Jason Coco
CFRunLoopRun() does not return until CFRunLoopStop() is called; hence, there's no way my main() can check isCancelled. I overrode the cancel method to call CFRunLoopStop(). So now to stop the thread, I'm doing [m_thread cancel] followed by [m_thread release] and that works. Question: is overriding the cancel method an OK thing to do? The documentation doesn't say one shouldn't.
Paul J. Lucas
It depends on what you're doing, but in general, I would say no, don't do that. The easiest way to do it would be to add a method like finishThread; I'll add a quick example to my answer. You should also do any clean-up you need to after the CFRunLoopRun() function returns.
Jason Coco