(UPDATED) this is the problem in a nutshell: in iOS I want to read a large file, do some processing on it (in this particular case encode as Base64 string()
First: in your original code, you were not using a background thread, but the main thread (dispatch_async but on the main queue).
When you schedule NSInputStream to run on the default runloop (so, the runloop of the main thread), the events are received when the main thread is in the default mode (NSDefaultRunLoopMode).
But: if you check, the default runloop changes mode in some situations (for example, during an UIScrollView scroll and some other UI updates). When the main runloop is in a mode different than the NSDefaultRunLoopMode, your events are not received.
Your old code, with the dispatch_async, was almost good (but move the UI Updates on the main thread). You have to add only few changes:
:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^{
// your background code
//end of your code
[[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point
});
:
[[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch
Try and let me know
EDIT - added example code:
To be more clear, I copy-paste your original code updated:
- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
// here: change the queue type and use a background queue (you can change priority)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
// here: start the loop
[[NSRunLoop currentRunLoop] run];
// note: all code below this line won't be executed, because the above method NEVER returns.
});
}
After making this modification, your:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {}
method, will be called on the same thread where you started the run loop, a background thread: if you need to update the UI, it's important that you dispatch again to the main thread.
Extra informations:
In my code I use dispatch_async on a random background queue (which dispatch your code on one of the available background threads, or start a new one if needed, all "automagically"). If you prefer, you can start your own thread instead of using a dispatch async.
Moreover, I don't check if a runloop is already running before sending the "run" message (but you can check it using the currentMode method, look NSRunLoop reference for more informations). It shouldn't be necessary because each thread has only one associated NSRunLoop instance, so sending another run (if already running) does nothing bad :-)
You can even avoid the direct use of runLoops and switch to a complete GCD approach, using dispatch_source, but I've never used it directly so I can't give you a "good example code" now
NSStream
requires a run loop. GCD doesn't provide one. But you don't need GCD here. NSStream
is already asynchronous. Just use it on the main thread; that's what it's designed for.
You're also doing several UI interactions while on a background thread. You can't do that. All UI interactions have to occur on the main thread (which is easy if you remove the GCD code).
Where GCD can be useful is if reading and processing the data is time consuming, you can hand that operation to GCD during NSStreamEventHasBytesAvailable
.