Reputation: 83
I am trying to make a searchbar to search for things I receive using NSURLConnection. right now, if I search for something, that string is send away as an URL with an asynchronous request, which gives me data.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[theConnection cancel];
[theConnection release];
theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
That data is parsed and when it is successful I post a notification
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
xmlParser = [[NSXMLParser alloc] data];
[xmlParser setDelegate:xmlGeocoder];
BOOL success = [xmlParser parse];
if(success == YES){
NSLog(@"No Errors");
[[NSNotificationCenter defaultCenter] postNotificationName:@"getArray" object:self];
}else{
NSLog(@"Error Error Error!!!");
[[NSNotificationCenter defaultCenter] postNotificationName:@"failToGetArray" object:self];
}
}
and my searchresultsTableView is reloaded.
self.array1 = [array2 copy];
[self.searchDisplayController.searchResultsTableView reloadData];
All these methods are depending on eachother, so B can't be executed, when A is still busy. I am using NSNotificationCenter to tell them to execute those code.
But I want to try NSOperation and I have no idea HOW to implement that. Do I have to put my search requests in an operation or every method I'm using?
Can someone give me a sample code to give me the idea how this should be done? Thanks in advance...
Upvotes: 0
Views: 880
Reputation: 11390
NSOperation is very useful. To use it you extend NSOperation and override the "main" method. In the main method you do your calculations/web request etc. So NSOperation is best for tasks you can wrap into a few simple steps, after each step you test if everything is good and either continue to the next step or cancel the operation. Once this is done you can simply instantiate your custom NSOperation and hand it off to a NSOperationQueue object and it will take care of the threading, starting, stopping cleaning up etc.
In the example below I have written a protocol to handle the completion of the task, I would advise you take this approach instead of using notification - unless you have multiple objects that needs to be notified instantly.
Make a new class that extends the NSOperation class:
//This object takes a "searchTerm" and waits to be "started".
#import <Foundation/Foundation.h>
@protocol ISSearchOperationDelegate
- (void) searchDataReady:(NSArray*) searchResult;
@end
@interface ISSearchOperation : NSOperation {
id <ISSearchOperationDelegate> delegate;
NSString *searchTerm;
}
@property(nonatomic, retain) NSString *searchTerm;
@property(nonatomic, assign) id delegate;
- (id) initWithSearchTerm:(NSString*) searchString;
@end
When an object extending NSOperation is added to an NSOperationQueue, the queue object tries to call a "main" method on the NSOperation, you must therefore wrap your task in this method. (notice that after each completed sub-task I test if it went well and "return" if not. The NSOperation class has a property called isCancelled This property can be set by the NSOperationQueue, so you must also test if that has been set during your completion of main. So to recap, you test from the inside of main if each step went as you wanted and you test if something on the outside has cancelled your task.):
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
[self performSelector:@selector(timeOut) withObject:nil afterDelay:4.0];
if ([self isCancelled]) return;
NSData *resultData = [self searchWebServiceForString:self.searchTerm];
if (resultData == nil) return;
if ([self isCancelled]) return;
NSArray *result = [self parseJSONResult:resultData];
if ([self isCancelled]) return;
if (result == nil) return;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[delegate performSelectorOnMainThread:@selector(searchDataReady:) withObject:result waitUntilDone:YES];
}
//I have not copied the implementation of all the methods I call during main, but I hope you understand that they are just "tasks" that each must be successfully completed before the next sub-task can be computed. So first of I put a timeout test in there, then I get my data from the web service and then I parse it.
Ok to get all this going you need a queue.
So in the class you want to be the delegate for this operation you do this:
somewhere set up a queue:
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[self setQueue:q];
[q release];
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[searchOperation setDelegate:self];
[queue addOperation:searchOperation]; //this makes the NSOperationQueue call the main method in the NSOperation
[searchOperation release];
}
//the delegate method called from inside the NSOperation
- (void) searchDataReady:(NSArray*) results {
//Data is here!
}
Some of the advantages with NSOperations is that from the caller point of view, we simply make an object, set a delegate, wait for the reply. But behind the scenes a series of threaded tasks that can be cancelled at any time is run, and in a manner that can handle if threaded stuff fails.
As you can see in the doSearch method it starts out by canceling any previous operations, I did this in an app where I would search a web service each time a user typed a letter in a word. That means that if the user searched for "hello world" - I would do a search for "h", then "he", then "hel", then hell", then "hello" etc. I wanted to stop and clean up the "h" task as soon as the user typed the "e", because it was then obsolete. I found out NSOperation was the only way that gave the responsiveness of threading and none of the mess that usually comes with spawning many threads on top of each other.
Hope you can use it to get started:)
Upvotes: 2