NSOperations or NSThread for bursts of smaller tasks that continuously cancel each other?
Asked Answered
A

1

3

I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.

The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.

The web service call and the subsequent parsing of the result I would like to move to a different thread.

I have written a simple SearchWebService class, it has only one public method: - (void) searchFor:(NSString*) str;

This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:

- (NSArray*) resultsReady;

I can't figure out how to get this functionality 'threaded'. If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one. Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:

- (void) keepRunning {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    SearchWebService *searchObj = [[SearchWebService alloc] init];
    [[NSRunLoop currentRunLoop] run]; //keeps it alive  
    [searchObj release];
    [pool release];
}

But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?

Hope someone could point me in the right direction, threading is giving me grief:) Thank you.

Afebrile answered 15/5, 2010 at 14:13 Comment(0)
A
2

Ok, I spend the last 8 hours reading up on every example out there. I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.

It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.

Is called after each keystroke:

- (void) searchFieldChanged:(UITextField*) textField {

    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    NSString *searchString = textField.text;

    if ([searchString length] > 0) {

        [self performSelector:@selector(doSearch:) withObject:textField.text afterDelay:0.8f];
    }
}

This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart. (I would have that a lot lower if it where not for the small touch keyboard).

If it is allowed to time out, it is time to search.

- (void) doSearch:(NSString*) searchString {

    [queue cancelAllOperations];
    ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
    [queue addOperation:searchOperation];
    [searchOperation release];
}

Cancel all operations that is currently in the queue. This is called every time a new search is started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.

The implementation for the ISSearchOperation is really simple:

@implementation ISSearchOperation

- (void) dealloc {

    [searchTerm release];
    [JSONresult release];
    [parsedResult release];
    [super dealloc];
}

- (id) initWithSearchTerm:(NSString*) searchString {

    if (self = [super init]) {

        [self setSearchTerm:searchString];
    }

    return self;
}

- (void) main {

    if ([self isCancelled]) return;
    [self setJSONresult:/*do webservice call synchronously*/];
    if ([self isCancelled]) return; 
    [self setParsedResult:/*parse JSON result*/];
    if ([self isCancelled]) return;

    [self performSelectorOnMainThread:@selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}

@end

There are two major steps, the downloading of the data from the web service and the parsing. After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.

I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.

But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop. NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.

Afebrile answered 16/5, 2010 at 12:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.