Iphone stop an ASIFormDataRequest - iphone

i have a problem in my viewController when i have a pending ASIFormDataRequest (started as an asynchronous task) that is still executing and the user presses the back button (in order to pop the view).
Is there any way to stop that asynchronous task?
I have read that is a method called "clearDelegatesAndCancel" but i don't know if it is what i'm looking for.
Thanks

Thing is, to call clearDelegatesAndCancel, you have to have a handle to the ASIFromDataRequest object that's running asynchronously. That means you should set it up as an property, like...
#interface MyViewController : UIViewController <ASIHTTPRequestDelegate>
{
ASIFormDataRequest *theRequest
...
}
#property (nonatomic, retain) ASIFormDataRequest *theRequest;
Then in your .m, don't declare a new request object, just assign your formdatarequest to the class's iVar:
#synthesize theRequest;
-(void)viewDidLoad //or whatever
{
self.theRequest = [ASIFormDataRequest requestWithUrl:myUrl];
// then configure and fire the request, being sure to set .delegate to self
}
-(void)viewWillDisappear:(BOOL)animated //or whatever
{
[self.theRequest clearDelegatesAndCancel];
}
-(void)dealloc
{
[theRequest release]; //don't not do this.
}
Point is, you need to set yourself up so that you've GOT the request to talk to while it's running asynchronously.
By the way, this is REALLY good practice. If your viewcontroller goes away (say by getting popped off the UINavController stack) before your request returns, it'll try to call the delegate method on a deallocated object, and boom.

From ASI docs (http://allseeing-i.com/ASIHTTPRequest/How-to-use)
To cancel an asynchronous request (either a request that was started with
[request startAsynchronous] or a request running in a queue you created),
call [request cancel]. Note that you cannot cancel a synchronous request.
Note that when you cancel a request, the request will treat that as an error,
and will call your delegate and/or queue’s failure delegate method. If you do
not want this behaviour, set your delegate to nil before calling cancel, or
use the clearDelegatesAndCancel method instead.

Related

Calling a method which executes delegate methods

I have a view controller called myViewController. Then i have an NSObject called myLogic. myLogic has a method called executeLogic() which will call some delegate methods. (Im guessing the delegate methods are called in separate threads). I am calling executeLogic() from myViewController and i want it to execute all the delegate methods before returning to execute the next command on myViewController. Right now i get this error
[URLRequestDBStoreLogic respondsToSelector:]: message sent to deallocated instance 0x5d2cff0
which i am guessing is because it returns to the main thread before the delegate methods are called? I am not sure. Any solutions? Will be deeply grateful. :)
This is where the URLRequestDBStoreLogic class is called (note this is an NSObject Class)
URLRequestDBStoreLogic * urlRequestLogicDBStoreLogic = [[URLRequestDBStoreLogic alloc]init];
[urlRequestLogicDBStoreLogic loadObjectsAtRemoteServer];
This is what the method that is called does
- (void) loadObjectsAtRemoteServer {
RKURL * url = [RKURL URLWithBaseURLString:#"http://y.co.uk/share/proxy/alfresco/slingshot/search?site=&term=&tag=x&maxResults=251&sort=&query=&repo=false"];
objectManager = [RKObjectManager managerWithBaseURL:url];
//Some other Code
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"?tag=x" delegate:self];
}
and this is where it breaks in the if statement.
// Setup the NSURLRequest. The request must be prepared right before dispatching
- (BOOL)prepareURLRequest
{
[_URLRequest setHTTPMethod:[self HTTPMethod]];
if ([self.delegate respondsToSelector:#selector(requestWillPrepareForSend:)]) {
[self.delegate requestWillPrepareForSend:self];
}
}

UISearchDisplayController - wait for N seconds OR for user to press "Search" before conducting search

I have a UISearchDisplayController that I am currently regretting implementing. The problem is that my search controller accesses a web service and then updates a UITableView by calling [tableView reloadData]. Every time a user enters a key, it searches. I've been able to partially relieve the symptoms by requiring at least three characters before calling the ASIHTTPRequest but there must be a better way. Basically I want the UISearchDisplayController to wait for N seconds OR for the user to press "Search" before searching. Here's what I have now:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
if (searchString.length > 2) {
ASIHTTPRequest *request = [IKRequestBuilder requestForIKDogFoodWithQueryString:searchString];
[request setDelegate:self];
[request startAsynchronous];
}
return NO;
}
#pragma mark - ASI HTTP Request
- (void)requestStarted:(ASIHTTPRequest *)request {
//show activity view over table view
}
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *response = [request responseString];
self.dogFoods = [IKJsonFactory buildIKDogFoodArrayWithJSON:response];
[self.searchDisplayController.searchResultsTableView reloadData];
}
Add an NSTimer as a property on your search delegate.
In the shouldReloadTableForSearchString:(NSString *)searchString delegate method, instead of sending the request straight away, instead check if you have a timer already, if so cancel it, then start a new timer.
In the timer's target method, kick off the search.
This will mean that while they are typing you will keep recreating the timer. When they stop typing the last incarnation of the timer will fire in n seconds. Then you just need to also add the manual kicking off of the search when they press the button. (you could also add a boolean to check if the search has already been started).
i.e. in your delegates interface
NSTimer *searchTimer;
#property (nonatomic, retain) NSTimer *searchTimer;
Then in your implementation of the shouldReloadTableForSearchString, instead of firing off the web request, do this:
if (self.searchTimer) {
[self.searchTimer invalidate];
self.searchTimer = nil;
}
self.searchTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(searchTimerPopped:) userInfo:nil repeats:FALSE];
Then in your timer target
-(void) searchTimerPopped:(NSTimer *)sTimer {
// code to fire off the asynchronous web call to do the search
}

Searching using NSOperation

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...
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:)

Is it safe to to self destruct an NSThread?

I have a pretty standard setup. I have a UIViewController to handle user interaction and a somewhat long running NSThread that does the actual work when the UI Controller shows up. One issue I've had is when I want to cancel the NSThread. Standard NSThread semantics is alright. I want the thread to finish, clean itself up (so that it releases the reference to my UIViewController) and then pop the UIViewController. So my code looks something like this:
In NSThread selector:
-(void) nsThreadWork
{
// do work here
#synchronized(self)
{
[nsThreadInstance release];
nsThreadInstance = nil;
}
}
In UIViewController that spawns the thread:
-(void) startThread
{
nsThreadInstance = [[NSThread alloc] initWithTarget:self
selector:#(nsThreadWork) ...];
[nsThreadInstance start];
[nsThreadInstance release];
}
And if I want to cancel:
// assume that this will be retried until we can execute this successfully.
-(void) cancelBackgroundOpAndPopViewController
{
#synchronized(self)
{
if (nsThreadInstance == nil)
{
[self popViewController];
}
}
}
I doubt if this is correct though. The problem is, UI elements can only be manipulated from the main thread. If I pop the view controller before NSThread exits, NSThread will exit and release the view controller which means it will be dealloced from NSThread's context which will cause an assert. Everything appears to work properly with the above code, but I dont understand when the runloop deallocates NSThread. Can anyone provide any insight?

webViewDidFinishLoad exception

I have a screen with a webView in my navigation controller stack and when I navigate from this view back to a previous before the page completely loaded I get a EXCEPTION_BAD_ACCESS. Seems, that the view with a webView being released before it comes to webViewDidFinishLoad function. My question is how to overcome this problem? I don't expect from the user to wait till the page loads... The code is very simple:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSURL *url = [NSURL URLWithString:storeUrl];
//URL Requst Object
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
//Load the request in the UIWebView.
[browser loadRequest:requestObj];
}
TIA
I'm guessing that you have your browser.delegate property set to your custom UIViewController. When you hit the back button, this controller is popped off the navigation stack and dealloc'd, leaving your browser.delegate pointing at freed memory. When your page finishes loading, the UIWebView calls a method on its delegate, which no longer exists, so you get EXC_BAD_ACCESS.
To prevent bugs like this, any time you set an object's delegate property, make sure you set that property to nil in your dealloc, like this:
- (void)dealloc {
self.browser.delegate = nil; // Prevents EXC_BAD_ACCESS
self.browser = nil; // releases if #property (retain)
[super dealloc];
}
Since nil silently swallows any messages it receives, this will prevent your EXC_BAD_ACCESS.