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
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.