Question

I have an operation "A" that use an async NSURLConnection to retrieve a list of IDs from a web service. When the response is received, the operation loop over these IDs and create an operation "B" for each ID and add them to the queue.

When I started testing with a maxConcurrentOperationCount to 1, I could not execute more than the A operation. So it seems that "B" operations added to the queue are still waiting for "A" completion.

The "executing" and "finished" properties are updated correctly and the KVO notification on "isFinished" is working as expected, but the queue still contains 2 operations, the "A" operation is never removed.

I tried to changed the code which was scheduling the connection in the NSRunLoop with a port, by a "performSelectorOnMainThread: @selector(start)", and it solved this problem, but It creates another one. When operations "B" are executed, depending on the server response they can start other non-concurrent operations "C" by calling their "start" method and not by adding them to the queue, and in this case I need that "B" does not finish until "C" has finished (this is why I'm using "start" and not adding "C" to the queue), and I do not want to execute "C" on the main thread which is what is happening when I use "performSelectorOnMainThread".

If anybody can help me to fix my problem with the runloop code, I tried to look at AFNetworking and other libraries but I don't see what I'm doing wrong.

- (void)send
{
    serverConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self startImmediately: NO];
    port = [NSPort port];
    runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort: port forMode: NSDefaultRunLoopMode];
    [serverConnection scheduleInRunLoop: runLoop forMode: NSDefaultRunLoopMode];
    [serverConnection start];
    [runLoop run];
}


- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)theError
{
    [runLoop removePort: port forMode: NSRunLoopCommonModes];
 // code handling error
}


- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
{
    [runLoop removePort: port forMode: NSRunLoopCommonModes];
 // code processing response
 // If this is executed on main thread, non-concurrent operations created and started from the response processing code, 
 // will be executed on main thread, instead of using the thread that was dedicated to this operation. 
}
Was it helpful?

Solution

According to the documentation for the NSRunLoop method run:

Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. OS X can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:

    BOOL shouldKeepRunning = YES;        // global
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

where shouldKeepRunning is set to NO somewhere else in the program.

Obviously, rather than using shouldKeepRunning, you might use ![self isFinished] instead, but it illustrates the point.

If you're going to enable multiple concurrent operations (and for the sake of efficiently running all of your B requests, you should seriously consider this), you might want to take a look at what AFNetworking did, which was (a) create a single dedicated thread for networking requests and start the run loop there, (b) schedule all network requests on this run loop; and (c) make the operations concurrent (e.g. isConcurrent returns YES).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top