Reputation: 12353
I have recently become thread curious on iOS. Please point me in the direction you would take, to achieve (if possible) the following on modern iOS devices... thank you!
The user is typing in text, say a word every few seconds.
From time to time I want to launch DifficultProcess to do some semantic processing. In short, I guess I need to be able to do four things:
What, essentially, are the calls one uses for A, B, C in modern (2011) (late January) iOS? I don't care about Dad's methods! And is "D" even possible in any way?
I guess those are the four ideas!
So in particular I want to send a message to, in other words call a routine in, the running background process (in that way, one could kill off the running background process if desired, or perhaps change it's mode of operation etc).
(For anyone born before 1997, you will recognise that as a typical "speculative processing" paradigm.)
Thanks for pointers for anyone who can be bothered on this!
Upvotes: 1
Views: 4177
Reputation: 2049
I would recommend using NSOperation and NSOperationQueue to manage background activity that you need to be able to cancel arbitrarily.
NSOperation's -cancel and NSOperationQueue's -cancelAllOperations are the methods to look at.
To get messages back from the background to the main thread, the dispatch_async-to-main-thread-queue technique is fine. You can combine this with a delegate protocol for your NSOperation to codify the messages you want to send back.
E.g.
@protocol MyOperationDelegate
- (void) operationStarted:(MyOperation *)operation;
- (void) makingProgressOnItem:(id)anItem otherInterestingItem:(NSDictionary *)otherItem remainingCount:(NSUInteger)count;
- (void) operationWillFinish:(MyOperation *)operation;
@end
@interface MyOperation
id <MyOperationDelegate> delegate;
@end
@implementation MyOperation
...
- (void) cancel
{
[super cancel];
// Tell the delegate we're about to finish (due to cancellation).
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationWillFinish:self];
});
}
- (void) main
{
// Check for cancellation
if (self.isCancelled) return;
// Starting
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationStarted:self];
});
if (self.isCancelled) return; // Another cancel check
// Send async progress messages periodically while doing some work
while (workNotDone)
{
// Do some work ...
dispatch_async (dispatch_get_main_queue(), ^{
[self.delegate makingProgressOnItem:foo otherInterestingItem:bar remainingCount:baz];
});
if (self.isCancelled) return;
}
// About to finish
if (!self.isCancelled) {
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationWillFinish:self];
});
}
}
@end
KVO is no good for interthread communication; the observation is received on the thread that originates the key value change. So, if your background thread changes a value, your background thread is going to receive the KVO about it. Probably not what you want.
Grandpa's -performSelectorOnMainThread:withObject:waitUntilDone: continues to be a fine way to get messages back to the main thread. The limitation is that your messages can only access one object-based argument. The dispatch_async to the main thread doesn't have this limitation.
If you want to fire off an asynchronous (or synchronous) NSNotification's from a background thread to the main thread, you need to use -performSelectorOnMainThread.
NSNotification *note = [NSNotification notificationWithName:FinishedABunchOfWorkNotification object:self userInfo:nil];
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:note waitUntilDone:YES];
Upvotes: 7
Reputation: 41821
I would suggest using dispatch_async to the global low priority queue (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)).
Cancellation is trickier though. There's no good general mechanism for canceling background work that I'm aware of aside from "chunking" it and checking a flag each chunk
To get messages back just dispatch_async back to the main queue. If you squint just right you can think of dispatch_async as "send message" in an actor model.
(edit) if you need serialization of stuff in the background, make a private queue and set its target to the global low priority one, iirc.
Upvotes: 3
Reputation:
At the risk of quoting Dad's method (it has been around since iPhone version 2) I use
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
It's easy and foolproof as long as you remember that you must create a new autorelease pool in the method you pass as selector, and drain it at the end of the method. Apart from that do whatever you like - EXCEPT touch UIKit. It isn't thread-safe so any UI changes must be done through
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
or KVO triggers. Key Value Observing would be a good way for your background thread to communicate to your main thread that the work is done.
- (void)myBackgroundThreadMethod {
NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
// my time-consuming processing here
[threadPool drain];
}
For more precise control of threads you need to look at NSThread. Threading Programming Guide lays it all out in detail - if you create a thread through NSThread then you have control over when the thread is started. The document does recommend leaving the thread alone and just letting it terminate - but shows how you can terminate it. One way is - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait
NSThread docs also say "leave priority alone". You can set thread priority with
+ (BOOL)setThreadPriority:(double)priority
but I've never known it to be necessary, the scheduler is smart enough to maintain UI responsiveness.
Upvotes: 2