I have some iPhone SDK 4.0 code which initializes an NSOperationQueue and then adds three classes (ClassA, ClassB, and ClassC) to run one after the other. ClassA, ClassB, and ClassC are all sub-classes of NSOperation.
The relevant code is included below.
ClassA *classA = [[ClassA alloc] init];
ClassB *classB = [[ClassB alloc] init];
ClassC *classC = [[ClassC alloc] init];
[classB addDependency:classA];
[classC addDependency:classB];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:classA];
[queue addOperation:classB];
[queue addOperation:classC];
[classA release];
[classB release];
[classC release];
[queue release];
The reason for the dependencies is because classB should only run if classA completes its operation successfully. Likewise, classC should only run if classB completes successfully.
At the moment I am having difficulty figuring out how to prevent, for example, classB from running if classA does not complete successfully. Continuing with this example, I was thinking of somehow evoking [NSOperationQueue cancelAllOperations] from within classA but I don't know how to get a handle on the parent NSOperationQueue from within classA (which is an NSOperation sub-class). This was just my initial thought, so I would be open to any other better suggestions for achieving the same outcome!
There is conditional code within each of the classes to determine whether they have completed properly - at the moment they are just NSLogging "Success" or "Fail" to the Console for debugging purposes. In a perfect world I would just like to be able to replace the NSLog(#"Fail") statement in each class with some code which will stop all of the other classes in the NSOperationQueue from running.
Any suggestions would be most welcome (and appreciated).
You could set a property in classA :
#property (readonly) BOOL completedSucessfully;
and set this to YES at the end of classA's main method.
Then, just check it at the start of classB.
- (void)main {
if (NO == [[dependencies objectAtIndex:0] completedSucessfully])
return;
Now, classB will just stop if classA reports failure.
NB You will probably need more error checking that in the example above i.e. making sure that you have dependencies, checking that it's the correct class etc.
- (void)main {
for (id *temp in [self dependencies])
if ([temp isKindOfClass:[ClassA class]])
if (NO == [(ClassA *)temp finishedSucessfully])
return;
I would suggest, if speed is not an issue, you can work synchronously. Else you can use:
[selector:#selctor(StartB) waitUntilTaskComplete:YES];
After viewing the WWDC 2015 session on advanced NSOperation techniques (highly recommended) I started using them in-depth in my own code. Here are some suggestions to achieve this
From within an NSOperation you can call [self currentQueue] to get "The operation queue that started the operation or nil if the queue could not be determined." You could then call cancelAllOperations on the returned queue. Empirically I have had difficulty using this approach because if you explicitly run code on the main queue, have code in a closures/block, or call a third party library, then the queue returned may not be the initial queue at all. In that situation calling cancelAllOperations will not result in the expected behavior - instead you are canceling the operations on a different queue.
Subclass NSOperation to include a property for the initial NSOperationQueue and subclass NSOperationQueue to set the property when the operation is added to the queue. Then call cancelAllOperations on self.initialQueue. This is the approach I'm using and works across all the scenarios mentioned above.
Instead of canceling all operations at the queue level, you can call the operation "cancel" method and finish your operation. If your operations have been written to conform to Apple's operation guidelines, they all check isCancelled when starting, and abort processing if true. It's a subtle difference: when you cancel the queue operations, any operations that haven't been started will not be started at all. When you set the operations to isCancelled, subsequent operations are started but (should) finish shortly there after. This allows scenarios where later operations might perform some cleanup, error handling, or user notification.
Related
I have a toolkit that I need to work with (to interface with a remote service). This toolkit queries the remote service and asks for results. It does this asynchronously, which in most cases is good, but not for creating concise methods. I want to make methods similar to the following:
-(NSArray *)getAllAccounts {
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
//Wait?
return result.records;
}
The problem is, inside the toolkit the methods call each other using #selector, not direct calls, so getting return values is difficult. Further, the actual query uses:
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:aRequest delegate:self] autorelease];
Which is asynchronous. By the time the data has been received from the service, my method has long ago returned... without the information. So my question is this: Is there a way to pause execution until the data has been returned? Could I accomplish this using a second thread to get the data while the main thread rests (or using 3 threads so the main thread doesn't rest?)
I don't want to edit the toolkit to change their method (or add a new one) to be synchronous, so is there a way to make a method as I want?
You might want to consider NOT making it all synchronous, especially if the sample code in your post is run on your main application thread. If you do that, the main thread will block the UI and the application will cease to respond until the remote transaction is complete.
Therefore, if you really insist on the synchronous approach, then you should definitely do it in a background thread so that the UI does not become unresponsive, which can actually lead to your App getting killed by the OS on iphone.
To do the work in a background thread, I would strongly recommend using the Grand Central Dispatch stuff, namely NSBlockOperation. It will free you from having to actually create and manage threads and makes your code pretty neat.
To do the synchronous thing, take a look at the NSCondition class documentation. You could do something like the following:
NSCondition* condition = ...;
bool finished = NO;
-(NSArray *)getAllAccounts {
[condition lock];
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
while (!finished)
[condition wait];
[condition unlock];
return result.records;
}
Then in the method called by the toolkit to provide the results you'd do:
- (void) queryResult:error:context: {
// Deal with results
[condition lock]
finished = YES;
[condition signal];
[condition unlock];
}
You'd probably want to encapsulate the "condition" and "finished" variables in your class declaration.
Hope this helps.
UPDATE: Here is some code to offload the work to a background thread:
NSOperationQueue* queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
// Invoke getAllAccounts method
}];
Of course, you can keep the queue around for later use and move the actual queuing of the work to inside your method call to make things neater.
The way to wait is to return from your current code. Finish up doing what you want done after the wait, in the asynchronous callback method you specify. What's so difficult about that?
Any synchronous waits in the main UI thread will block the UI and make the user think your app has locked up, which is likely far worse than your thinking the code isn't concise enough.
I have a library project that uses ASIHTTPRequest to make URL requests and parse the responses. The library will be used by a separate iPhone app project.
If my iPhone controller code responds to a touch event, then calls into the library to make URL requests, how do I best perform the requests asynchronously?
In the library, if I use the delegate pattern for asynchronous requests as shown in the ASIHTTPRequest sample code, how do I return data from the library back to the calling code in the iPhone controller?
If I instead make synchronous URL requests with ASIHTTPRequest inside the library, what's the easiest way to put the calls to the library from the iPhone controller on a separate thread to avoid tying up the UI thread?
I'm no ASIHTTPRequest expert (NSURLRequest has always done me fine), but from a quick poke at the code, it looks like you'd use its delegate and didFinishSelector properties to give it someone to tell when the URL request is finished. So, for example:
- (void)startURLRequest
{
ASIHTTPRequest *myRequest;
/* code to set the request up with your target URL, etc here */
myRequest.delegate = self;
myRequest.didFinishSelector = #selector(HTTPRequestDidFinish:);
/* ... */
[myRequest startAsynchronous];
}
- (void)HTTPRequestDidFinish:(ASIHTTPRequest *)request
{
NSLog(#"Request %# did finish, got data: %#", request, request.data);
[myTargetForData didReceiveData:request.data fromURL:request.originalURL];
}
Apple explicitly recommend that you use the built-in runloop style mechanisms for asynchronous HTTP fetching, not separate threads. Using separate threads is likely to result in worse performance — at least in terms of battery life and/or device heat, even if it's still fast enough.
That said, as a learning point, by far the quickest way to switch something onto a separate thread and have it report back to the main thread (remember: UIKit objects may be messaged only from the main thread) is by changing this:
- (void)postResult:(NSString *)result
{
instanceOfUILabel.text = result;
}
- (void)doExpensiveOperationOn:(NSString *)source
{
/* lots of expensive processing here, and then... */
[self postResult:result];
}
- (IBAction)userWantsOperationDone:(id)sender
{
[self doExpensiveOperationOn:#"some value or another"];
}
Into this:
- (void)postResult:(NSString *)result
{
instanceOfUILabel.text = result;
}
- (void)doExpensiveOperationOn:(NSString *)source
{
/* we're on a thread without an autorelease pool now, probably we'll want one */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* lots of expensive processing here, and then... */
/* in this simplified example, we assume that ownership of 'result' is here on this thread, possibly on the autorelease pool, so wait until postResult has definitely finished before doing anything that might release result */
[self performSelectorOnMainThread:#selector(postResult:) withObject:result waitUntilDone:YES];
[pool release];
}
- (IBAction)userWantsOperationDone:(id)sender
{
[self performSelectorOnBackgroundThread:#selector(doExpensiveOperationOn:) withObject:#"some value or another"];
}
There's about a million possible concurrency errors you can make by just going threaded without thinking about it though, and in that example an obvious problem is that whatever triggered the IBAction can [probably] trigger it several more times before doExpensiveOperationOn has finished. Multithreading is not something to be dashed into lightly.
For anyone's future reference, the easiest approach I found is to use the async request functionality built into ASIHTTPRequest, setting my library object as the delegate and setting the didFinishSelector: and didFailSelector: values to different methods inside my library for each request.
At the end of processing each response, I assign the parsed response (an NSString* or NSArray*) to a property of my library object instead of returning a value.
When my iOS view controller delegate is loaded, I add a change observer to each of the properties in the library using Key-Value Observing. When the response is parsed and assigned to the property in the library, the observeValueForKeyPath:ofObject:change:context: method is called in the code of my view controller delegate, and from there I can figure out which property was changed and therefore what UI needs to be updated.
here is what i want:
create an object that 'lives' in its own thread, all the methods should be executed in that thread.
i.e:
// i'm in the main thread
MyClass *myObject = [ [MyClass alloc] init ]; // it creates its own thread
[myObject method1]; // should execute the method1 in myObject's thread
[myObject method2]; // should execute the method2 in myObject's thread
[myobject release]; // should deallocate everything that is used for myObject and remove myObject's thread
i have been reading about threads and runloops. I created a new thread on the init method, its entry point is the runloop method. The runloopMethod just set the most basic stuff needed for running a NSRunLoop and runs it.
aThread = [[NSThread alloc] initWithTarget:self selector:#selector(runloopMethod) object:nil];
[aThread start];
it worked fine, but when i call a method ( i.e: [myObject method1];) from the main thread it runs it on the main thread, how do i know it?, well, because method1 performs several operations that blocks the UI. What i have done is to redirect the call in this way:
// on MyClass.m
-(void) method1 {
if ([NSThread currentThread] != aThread) {
[self performSelector:#selector(method1) onThread:aThread withObject:nil waitUntilDone:YES];
}else {
// do my stuff
}
it's working, but this way limits me, also i have some questions for you:
i have realized that if i'm in X-thread and call a method of some object, it will be executed in X-thread. I think that the method call will be added (not sure if it's the word) to the X-thread's runloop. right?
Is there a way to set that: any call to my object's methods will be executed on the object's thread? (without doing all this stuff).
also, is it the correct way for what am i doing?
method1, method2, and so on are the sync version of my functions..so they will block the UI. that' why i assume having another thread is the way.
thanks for reading!.
btw. i'm not using GCD since i need to support iOS 3
The Objective C method dispatch runtime code has no mechanism (AFAIK) to determine implicitly whether to do a generic method call on a different thread than the current one, so you will have to implement your own explicit background call mechanism, as you did, using performSelector.
If you set waitUntilDone to YES on your call to your background thread from the main thread, you will still block the UI.
If you want your method1 to run in the background and not block the UI, set waitUntilDone to NO, and have to background thread inform the main thread about completion (or anything else) using performSelectorOnMainThread.
You might alternatively be able to use operation queues to send messages to your background thread's run loop.
I'm guessing you are trying to use threads to run background tasks in order to keep the UI responsive. That's good, but this would be a very difficult approach. Try this instead:
1) From the main thread, fire off a new thread:
[NSThread detachNewThreadSelector:#selector(methodThatTheThreadWillRun)
toTarget:nil
withObject:nil];
2) Write methodThatTheThreadShouldRun and do whatever you need to do in it. It will be executed in the thread you just created. When it finishes, have it call a threadIsFinished on the main thread:
- (void)methodThatTheThreadWillRun {
MyClass *myObject = [ [MyClass alloc] init ];
[myObject method1];
[myObject method2];
[myobject release];
[self performSelectorOnMainThread:#selector(threadIsFinished)];
}
3) Finally, write threadIsFinished:
- (void)threadIsFinished {
// do whatever you need to do here: stop a spinner, etc.
// this will be invoked by the background thread but will
// execute on the main thread
}
this is my first question here, so excuse me if I made any mistakes!
In my iPhone project I have a method running in a thread which takes a long time to execute (that's why it runs in a thread).
[NSThread detachNewThreadSelector:#selector(methodToBeCalledInAThread) toTarget:self withObject:nil];
// ...
-(void)methodToBeCalledInAThread {
MyClass *myClass = [[MyClass alloc] init];
[myClass setDelegate:self];
[myClass veryIntensiveComputing];
[myClass release];
}
My goal is to notifiy the ViewController calling this method of any progress going on in this method. This is why I set the ViewController as a delegate of the class.
Now in the expensive method I do the following:
if(self.delegate != nil) {
[self.delegate madeSomeProgress];
}
But unfortunately this does not work, because (I think) I'm in a background thread.
How do I achieve to notify the delegate of any changes with the method being executed asynchronously?
Try [self.delegate performSelectorOnMainThread: #selector(madeSomeProgress) withObject: nil waitUntilDone: YES];....
See the documentation for details.
This will synchronously perform the operation on the main thread. As long as nothing in the main thread tries to execute stuff in that secondary thread, this is mostly safe to do.
However if you don't want to block the computation until the main thread services the request, you can pass NO to not wait. If you do so, then you also have to worry about thread synchronization. The main thread may not immediately service the request and, when it does, your background thread may be in the process of mutating state.
Threads are hard.
I'm using an NSOperationQueue to manage HTTP connections (using ASI-HTTPRequest). Since I have multiple views and the need to have these different views requesting HTTP connections, should I try to create a global NSOperationQueue in the app delegate, or should I have one in each of the views? I'm not familiar with NSOperationQueue.
I'd like to know a) what the best practice is and b) if there is no best practice, what the tradeoffs are if any.
I did try to put the operation queue in the class (as a property) where I handle the server connections but the task never fired. Couldnt figure it out but [queue operations] = 0. If someone knows a solution to this, I presume this would be the best place to put it.
I have solved this by adding a class method on NSOperationQueue that I think Apple has missed; a shared operation queue. I add this as a category on NSOperationQueue as this:
// NSOperationQueue+SharedQueue.h
#interface NSOperationQueue (SharedQueue)
+(NSOperationQueue*)sharedOperationQueue;
#end
// NSOperationQueue+SharedQueue.m
#implementation NSOperationQueue (SharedQueue)
+(NSOperationQueue*)sharedOperationQueue;
{
static NSOperationQueue* sharedQueue = nil;
if (sharedQueue == nil) {
sharedQueue = [[NSOperationQueue alloc] init];
}
return sharedQueue;
}
#end
This way I do not need to manage a whole bunch of queues unless I really need to. I have easy access to a shared queue from all my view controllers.
I have even added a category to NSObject to make it even easier to add new operations on this shared queue:
// NSObject+SharedQueue.h
#interface NSObject (SharedQueue)
-(void)performSelectorOnBackgroundQueue:(SEL)aSelector withObject:(id)anObject;
#end
// NSObject+SharedQueue.m
#implementation NSObject (SharedQueue)
-(void)performSelectorOnBackgroundQueue:(SEL)aSelector withObject:(id)anObject;
{
NSOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:aSelector
object:anObject];
[[NSOperationQueue sharedOperationQueue] addOperation:operation];
[operation release];
}
#end
My personal preference for this is to have a singleton that manages all http requests. Each view would then ask the singleton to make the http call, passing itself as a delegate for that call then the singleton hands that delegate and call off to an NSOperation and then NSOperation calls back once the call is done.
If you already have a pointer to a class that handles connections in each view/view controller, there's no reason you would also need to have a pointer to the operation queue.
I suppose what you want to do is something like: a) view(Controller) hands url(+data) to server handling object, b) server handling objects creates operation and puts it in a queue that it and only it has a pointer to.
It's hard to figure out why that didn't work if you don't provide more detail.
I highly recommend taking a look at ASIHTTPRequest which provides a NetworkQueue class to handle this kind of task. It has several convenient delegate fields that lets you register to keep track of progress, know when a download or upload finished etc.