iOS: ASIHTTPRequst synchronous on background selector bad idea? - iphone

I was using NSURLConnection in a synchronous way before running on a background selector, so when I moved over to ASIHTTPRequest I did the same with this framework.
So, is it a bad idea to do something like the following?
// From another method
[self performSelectorInBackground:#selector(callDatasource) withObject:nil];
- (NSData *)callDatasource {
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:someURLthatIamusing];
[request setTimeOutSeconds:50.0];
[request startSynchronous];
NSError *error = [request error];
NSData *returnedData;
if (!error) {
returnedData = [request responseData];
} else {
// do something with error
}
[self performSelectorOnMainThread:#selector(done) withObject:nil waitUntilDone:NO];
[apool release];
return returnedData;
}//end
What would be the advantage to use the ASIHTTPRequest and asynchronous methods along with the delegate methods?

From experience, sometimes odd things can happen when using ASIHTTPRequest synchronous requests off a secondary thread: the download activity icon in the status bar not disappearing upon download completion is one issue I've noticed from time to time. I've had no major problems in the past, but I use the asynchronous methods now rather than your approach. The ASI asynchronous methods are by the nature of being a widely used library more highly tested than my own implementation could ever be.
There are a number of advantages with using the asynchronous methods - you mention the delegate methods, but the latest release of ASI actually also supports blocks, which is a great leap forward (dealing with multiple synchronous calls used to be a bit of a pain due to the shared delegate methods (or unique delegates for each asynchronous call). But with blocks you can now get rid of the delegates entirely. I've found them to be really useful. Plus if you use multiple contributors it can make readability a lot easier.

Also, by doing it Async, you can more easily track progress through the setProgressDelegate command.

Related

How to keep the ASIHTTPRequestDelegate even when if the user popped from that view controller?

I have an application in which I need to have a settings page,which has some credentials of the user then he can edit that.its a table view loading from an array taken from the httprequest.by clicking on each of this it will have the option to go to another view and update that value and come back. I have done the update call to the server on that update view like this..
dispatch_async(backgroundQueue_, ^{
[self performSelectorInBackground:#selector(load) withObject:nil];
dispatch_sync(dispatch_get_main_queue(), ^{
[self showHUD];
});
because in the mainqueue i am doing the popping back operation.so i need that update service to be called in the background.But the problem is when i coming back i am calling another service in the settings viewcontroller.to load the updated value.some times the delegates of the request is getting crashed.I am calling the service like this.
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:uidstr forKey:#"userId"];
request.userInfo=[NSDictionary dictionaryWithObject:#"update" forKey:#"type"];
[request setPostValue:self.string forKey:#"age"];
[request setDelegate:self];
[request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
[self showHUD1];
[request startAsynchronous];
Can anybody point me how i can do this with out crashing my app.I think the problem is the delegate getting nil.
Decouple your network requests from your view controller code. That way if the views are unloaded, the network delegate will still exist.
For example: Make a NetworkRequest singleton class that does all the communication with the network, and then you could use a mechanism like NSNotifications, or an #protocol interface in the singleton class that view controllers could become delegates of, to pass results and status changes as needed.
For a good tutorial on singletons in Objective-C, see: http://www.galloway.me.uk/tutorials/singleton-classes/
Better drop usage of ASIHTTP since this framework is no longer supported by the developers. You won't get support for possible changes of future iOS versions.

ASIHTTPRequest in ASINetworkQueue: Cancel request while queue is running

I am using ASIHTTPRequest (I know, I know, it is not being maintained - that doesn't matter; it works and it is what my app is built on) to run a series of HTTP requests through ASINetworkQueue.
The problem is that my queue will have many requests (thousands), and it will take a while to run. In the time that it is running, some of the data in the app may have changed which would make some of the request unnecessary. I would like to run a validation method on each request right before it runs, and if the validation method does not check out, then it will cancel that request and go on to the next one.
Right now, I am using the following code to create my ASIHTTPRequests:
ASINetworkQueue *myQueue = [ASINetworkQueue queue];
NSURL *URL = [NSURL URLWithString:#"http://mywebsite.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setStartedBlock:^{
NSLog(#"Request started: %#", request.url.absoluteString);
}];
[request setCompletionBlock:^{
NSLog(#"Request completed: %#", request.url.absoluteString);
// do some code here to clean up since it's finished
}];
[myQueue addOperation:request];
My current thinking is to put something into the startedBlock, so it would do:
[request setStartedBlock:^{
NSLog(#"Request started");
if (![self myValidationMethod]) {
[request cancel]; // <----------
}
}];
However when I do this, I get the following warning from Xcode:
"Capturing 'request' strongly in this block is likely to lead to a retain cycle."
First, is this the right method to go about doing this? I can't seem to find a way to remove a specific ASIHTTPRequest from an ASINetworkQueue. Second, is this warning from Xcode something that I will need to worry about?
About the warning you capture the blocks 'container' and form a cycle... just say:
__weak ASIHTTPRequest *rw = request;
and use that in the block.
as for the started block approach. doesnt sound perfect to me but I dont know a better approach...

How to load content into TableView without blocking the UI?

I'm working on a TableView which controller downloads data from a web feed, parse and populate its content in this TableView. The feed provides data in chunks of 10 items only. So, for example loading data when there are 112 items could require about 12 requests to server. I would like to make these requests without blocking user screen and it should load data in order, like it can't load items on page 5 unless it has already fetched previous one (1,2,3,4 in this exact order for the example).
Any idea on how to implement this ?
Thx in advance for your help,
Stephane
Make your web calls asynchronous. Dont do web calls on the main UI thread...
For example if you are using ASIHttp library to make http calls (this is built on top of Apple's NSURLConnection), making an async request is as simple as -
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
And when the data is fetched these selector callbacks are invoked -
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
This will definitely make your UI responsive...
Also keep in mind to update UI elements only on the main thread. It's easy to start updating ui elements from background threads. So keep in mind...
You do not need to use another API and can use Apple's own NSURLConnection. It can retrieve the data synchronously or asynchronously. Of course the latter is necessary in your case. You save the data in the requests's delegate methods.
– connection:didReceiveResponse:
– connection:didReceiveData:
– connection:didFailWithError:
– connectionDidFinishLoading:
Also, see my recent more complete answer to this question.

Iphone: Need release using ASIFormDataRequest

i have one simple question, if i'm using ASIFormDataRequest when i need to release the request object?
NSURL *url = [NSURL URLWithString:#"url"];
ASIFormDataRequest *requestForm = [ASIFormDataRequest requestWithURL:url];
[requestForm addPostValue:[[NSUserDefaults standardUserDefaults] stringForKey:#"user"] forKey:#"user"];
[requestForm setRequestMethod:#"POST"];
[requestForm setDelegate:self];
[requestForm startAsynchronous];
Thanks
You need to remember that you are always responsible for releasing an object if the method from which you receive it contains new, copy, or init.
In this case, you don't need to release it. The ASIHTTPRequest class autoreleases it for you.
A quick search in the implementation file shows that it'll be autoreleased.
+ (id)requestWithURL:(NSURL *)newURL
{
return [[[self alloc] initWithURL:newURL] autorelease];
}
Your request is autoreleased in your code, so you don't need to release it (as others have said).
However, you are starting an asynchronous request - it'll complete/fail sometime in the future, and if it is associated with other objects which will get freed when the view exits you're potentially leaving yourself open to a crash. So I'd suggest you would want to make requestForm a property of your class (so when you assign the request to self.requestForm it will get retained for you), and explicitly release & nil it when the request completes.
If it's a very simple app with just one view you may get away without that though.

Making multiple service calls on iPhone app initialization

I need to make multiple asynchronous service calls in the application:didFinishLaunchingWithOptions: method from my application delegate in order to retrieve some data from a service to be used across various controllers in my app. I have control over the service, and I've designed the API to be as RESTful as possible, so I need to make multiple calls during app initialization.
What I want to do is to show a loading view with a progress indicator - similar to the default splash screen from Default.png - and remove that view once the service calls have completed and I have the initial values I need. This is pretty easy to do if there's only one service call, since I can simply hook that logic into the connectionDidFinishLoading: delegate method of NSURLConnection by hiding the loading view and displaying the root controller.
However, with multiple service calls, it becomes tricky. I can "chain" everything together and fire off one request, wait for it to finish/fail, then fire off the second request, and so on until I get to the last request. In the last request, I then hide the loading view and display the normal view. However, this can get unwieldy with multiple service calls, and the code becomes hard to understand and follow.
Any suggestions on the best approach for this?
I'm thinking one solution is to have a singleton class responsible for making service calls and app initialization. The singleton object will fire off all necessary requests in parallel on start, and each fail/finish callback will check if every request has finished. If all requests have finished, then it can call some method in the application delegate and tell it to hide the loading view, show the root controller, etc.
Thoughts?
Another possibility is to have each service completion callback notify (NSNotification) the controller of the progress indicator that progress has been made. You could also tell the controller of the progress indicator of how many request you were planning to make, and let it keep score, and itself do a callback when it thinks everything is done.
I am doing something similar with an NSOperationQueue that is configured to just run 1 operation at a time. See for example WeaveEngine.m and it's synchronizewithServer:credentials: method. I queue up all the separate operations, which are mostly async network calls.
you could use NSThreading and make synchronous calls in separate threads for each thing you need to get like
[NSThread detachNewThreadSelector:#selector(getDataRequest1:) toTarget:self withObject:urlRequest];
[NSThread detachNewThreadSelector:#selector(getDataRequest2:) toTarget:self withObject:urlRequest];
[NSThread detachNewThreadSelector:#selector(getDataRequest3:) toTarget:self withObject:urlRequest];
then in the selector for each thread do something like
- (void) getDataRequest1:(NSURLRequest*)urlRequest {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSHTTPURLResponse *urlResponse;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&urlResponse error:&error];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if ([urlResponse statusCode] < 200 || [urlResponse statusCode] > 299) {
//request probably failed
}else{
[self performSelectorOnMainThread:#selector(completeRequest1:) withObject:responseData waitUntilDone:NO];
}
[pool drain];
[responseString release];
[urlRequest release];
}
of course it really depends on how many requests/threads you are wanting to spawn.
and you will need to keep track of how many you spawn vs how many finish so you can properly stop your spinner.