Search code examples
iosobjective-cuitableviewnsoperationnsoperationqueue

Run parallel code in an NSOperationQueue with an NSOperation


I'm experiencing kind of a problem here. I'm developing an app that reads files and displays its content in an UITableView. I realised recently that files might get real big and that I'd need to code the actual reading of the files asynchronously. My project is already pretty big and today I managed to wrap the code I already had in an NSOperation.

What I did is that my parser (opening and reading my files) is now called within an NSOperation. Just like that :

@implementation ReadPcapOperation

@synthesize parser =_parser;

- (id) initWithURL:(NSURL *)url linkedTo:(PacketFlowViewController *)packetController
{
    self = [super init];
    if (self) {
        _parser = [[PcapParser alloc] initWithURL:url linkedTo:packetController];
    }
    return self;
}

- (void)main {
    // a lengthy operation
    @autoreleasepool {
        if (self.isCancelled)
            return;

        [_parser read];

    }
}

@end

I give you here only the implementation, there's nothing important in the .h file. This NSOperation subclass is called within an NSOperation :

_queue = [NSOperationQueue new];
_queue.name = @"File Parsing Queue";
_queue.maxConcurrentOperationCount = 1;
[_queue addOperation:_readFileOperation];

_readFileOperation is an instance of ReadPcapOperation described above.

Now, when I test my code, still no difference, when I open a file the UI is still blocked while the file content is loading into my UITableView. I tested with this condition :

[NSThread isMainThread]

This test returns NO in main from ReadPcapOperation, which is good, exactly what I need. But this test returns YES when I put it inside the method "read", message sent to an object inside main from ReadPcapOperation ! So my whole code is still running on mainthread and blocks my UI.

What am I missing here, guys ?

Let me know if you need more explanation !

EDIT :

Here is what's weird : I'll post a part of my code that's supposed to be executing in a background thread.

- (void) read
{
    if ([NSThread isMainThread])
        NSLog(@"read: IT S MAIN THREAD");
    else
        NSLog(@"read: IT S NOT MAIN THREAD");

    [_fileStream open];
}

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
    switch(eventCode)
    {
        case NSStreamEventOpenCompleted:
        {
            //We read the pcap file header
            [self readGlobalHeader];
            [self readNextPacket];
            break;
        }
        case NSStreamEventHasBytesAvailable:
        {
            //We read all packets
            [self readNextPacket];
            break;
        }
        case NSStreamEventNone:
        {
            break;
        }
        case NSStreamEventHasSpaceAvailable:
        {
            break;
        }
        case NSStreamEventEndEncountered:
        {
            NSLog(@"End encountered !");
            [_fileStream close];
            [_fileStream removeFromRunLoop:[NSRunLoop currentRunLoop]
                              forMode:NSDefaultRunLoopMode];
            //_fileStream = nil;
            break;
        }
        case NSStreamEventErrorOccurred:
        {
            NSError *theError = [stream streamError];
            NSLog(@"Error %i stream event occured. Domain : %@.", theError.code, theError.domain);
            [stream close];
            break;
        }
    }
}

- (void) readGlobalHeader
{
    if ([NSThread isMainThread])
        NSLog(@"readGlobalHeader: IT S MAIN THREAD");
    else
        NSLog(@"readGlobalHeader: IT S NOT MAIN THREAD");
    int sizeOfGlobalHeader = 24;

Up here you see the method read that I call directly from the NSOperation. The Log there says :"NOT MAIN THREAD". SO far, so good. read opens the NSInputStreamObject and the delegate will then call "handleEvent" and it's at this moment that I read the bytes. When I call "readGlobalHeader" and read the first bytes of my file, the log is "MAIN THREAD". Shouldn't it be in the background thread as well ? I'm really lost there !

What might be important to note is that when I initialize the stream, before caling "read" I set it up with this line of code (I'm not sure it's the cause) :

[_fileStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

EDIT 2: Here's the backtrace after the breakpoints. The behavior is just as I describe it above. (not enought reputation to post images)

Breakpoint at the read method Breakpoint after the stream is open


Solution

  • Thank you all for your answers they've been so helpful. I solved my problem and here's how :

    First my stream delegates method were still called in the main thread and I didn't know why. THis was because I initialized the stream in the init method of my NSOperation class and not in main. So when I called [_fileStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; it was scheduling in in the main thread loop. I solved this issue by initializing _filestream in the NSOperation main and it ran correctly in a background thread.

    Then I realized that the background thread was actually finished and destroyed before the delegate's method were called. At this point I completely changed my way of doing background processing. I simply used, as a couple of you suggested, GCD with a dispatch_async. This is actually where I'm using it:

    - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
        switch(eventCode)
        {
            case NSStreamEventOpenCompleted:
            {
                //We read the pcap file header
                dispatch_async(myqueue, ^
                {
                         [self readGlobalHeader];
                         [self readNextPacket];
                 });
                break;
            }
            case NSStreamEventHasBytesAvailable:
            {
                //We read all packets
                [self readNextPacket];
                break;
            }
    

    and it works perfectly. I'm refreshing my UI with a method that I send to the main queue.