Seems like people are going to a lot of effort to rewrite NSRunloop. Per the NSRunloop documentation:
Your application cannot either create
  or explicitly manage NSRunLoop
  objects. Each NSThread object,
  including the application’s main
  thread, has an NSRunLoop object
  automatically created for it as
  needed.
So surely the trivial answer would be, to create a usable queue:
- (void)startRunLoop:(id)someObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [[NSRunLoop currentRunLoop] run];
    [pool release];
}
...
NSThread *serialDispatchThread = [[NSThread alloc] 
                   initWithTarget:self 
                   selector:@selector(startRunLoop:) 
                   object:nil];
[serialDispatchThread start];
To add a task to the queue:
[object
    performSelector:@selector(whatever:) 
    onThread:serialDispatchThread
    withObject:someArgument
    waitUntilDone:NO];
Per the Threading Programming Guide section on Run Loops:
Cocoa defines a custom input source
  that allows you to perform a selector
  on any thread. ... perform selector requests are
  serialized on the target thread,
  alleviating many of the
  synchronization problems that might
  occur with multiple methods being run
  on one thread.
So you've got an explicitly serial queue. Of course, mine isn't fantastically written because I've told the run loop to run forever, and you may prefer one you can terminate later, but those are easy modifications to make.