I have an NSRunLoop object, to which I attach timers and streams. It works great. Stopping it is another story alltogether.
I run the loop using [runLoop run]
.
If I try to stop the loop using CRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop])
, the loop won't stop. If I start the loop using CRunLoopRun()
instead, it works. I have also made sure that the call is made on the correct thread (the one running my custom run loop). I have debugged this with pthread_self()
.
I found a mailing list archive, where a developer said "don't bother using CRunLoopStop()
if you started the loop using the run method of NSRunLoop
". I can understand why it is the way it is - you normally pair up initializers and finalizers from the same set of functions.
How do you stop an NSRunLoop
without "resorting to CF"? I don't see a stop
method on NSRunLoop
. The docs says that you can stop a run loop in three ways:
CFRunLoopStop()
Well, I already tried 2. and there's an "ugly" feel to it, because you have to dig into CF. 3. is out of the question - I don't fancy non deterministic code.
This leaves us with 1. If I understand the docs correctly, you cannot "add" a timeout to an already existing run loop. You can only run new run loops with a timeout. If I run a new run loop, it will not solve my problem, as it will only create a nested run loop. I'll still pop right back into the old one, the same I wanted to stop... right? I might have misunderstood this one. Also, I don't want to run the loop with a timeout value. If I do, I'll have to make a trade off between burning CPU cycles (low timeout value) and responsiveness (high timeout value).
This is the setup I have right now (pseudo code-ish):
Communicator.h
@interface Communicator : NSObject {
NSThread* commThread;
}
-(void) start;
-(void) stop;
@end
Communicator.m
@interface Communicator (private)
-(void) threadLoop:(id) argument;
-(void) stopThread;
@end
@implementation Communicator
-(void) start {
thread = [[NSThread alloc] initWithTarget:self
selector:@selector(threadLoop:)
object:nil];
[thread start];
}
-(void) stop {
[self performSelector:@selector(stopThread)
onThread:thread
withObject:self
waitUntilDone:NO];
// Code ommitted for waiting for the thread to exit...
[thread release];
thread = nil;
}
@end
@implementation Communicator (private)
-(void) stopThread {
CRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]);
}
-(void) threadLoop:(id) argument {
// Code ommitted for setting up auto release pool
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
// Code omitted for adding input sources to the run loop
CFRunLoopRun();
// [runLoop run]; <- not stoppable with
// Code omitted for draining auto release pools
// Code omitted for signalling that the thread has exited
}
@endif
What am I to do? Is it common/a good pattern to mess around with CF? I don't know Foundation well enough. Is interfering in the CF layer possibly dangerous (with respect to memory corruption, inconsistencies, memory leaks)? Is there a better pattern to achieve what I am trying to achieve?
You're doing well. There's not problem to use CoreFoundation when you cannot achieve your goal with Foundation. As CoreFoundation is C, it's easier to mess up with memory management but there is no intrinsic danger in using CFRunLoop
rather than NSRunLoop
(sometimes it may even be safer: CFRunLoop
API are thread-safe whereas NSRunLoop
isn't).
If you want to stop your NSRunLoop
, you can run it using runMode:beforeDate:
. runMode:beforeDate:
returns as soon as an input source is processed so you don't need to wait until the timeout date is reached.
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSDate *date = [NSDate distantFuture];
while ( !runLoopIsStopped && [runLoop runMode:NSDefaultRunLoopMode beforeDate:date] );
Then, to stop the run loop, you just need to set runLoopIsStopped
to YES
.