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.
Related
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.
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...
I am having problems in my iphone application due to weak wifi signals. My application uses webservice to retireve data from our server but when Wifi signals are weak the response never comes back and user gets stuck on "Loading..." overlay screen. Finally the application crashes at the end. How can i handle this situation gracefully. Is there a way to set TimeOut for my webservice calls or something like this?
Thanks, Asif.
try to use ASIHTTP lib
http://allseeing-i.com/ASIHTTPRequest/How-to-use
You might want to learn about ASIHTTPRequest as it features much more than the standard CFNetwork api. The code is straight forward, error handling as well:
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (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];
}
You can set the NSURLConnection timeout and a delegate to respond to connection:didFailWithError: selector. See this S.O. topic.
if your delegate is never being called, this is a somehow known issue.
The only workaround seems to be setting your own NSTimer to fire after some time and cancel the request. It is definitely awkard, but it should not be that complex.
If you are curious about the reason behind the issue with timeout, it seems to be related to the slow starting of the 3G subsystem in an 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.
I am loading an image from an API in my tableviewcell, it downloads the image everytime I scroll down the UITableView. So what is the best way to save this image and so it doesn't have to download the image again if it is already there?
Same thing for text, how can I save the text...
If it's not very big, you can download it once and save it into user preferences (NSUserDefaults) as NSData object. Works for me.
Alternatively, you can use asynchronous requests with NSUrlConnection and implement caching in any way you like. (For example, update image only once a week.)
Moreover, even default cache settings of NSUrlConnection might work good enough.
More on caching
edit
An example of asynchronous request.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: url]];
URLConnectionDelegate *delegate = ...;
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:delegate];
if (!connection) {
// something went wrong
}
In delegate, you mainly need methods to handle received data and to finish connection.
Assume you have NSMutableData *receivedData object.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// success, do whatever you want with data
[receivedData release];
[connection release];
}
The link above and API documentation provide more details about delegate structure.
Look at the LazyTableImages sample application in the iOS site sample code section. It has a great example of how to download images for a table cell using NSUrlConnection asynchronous calls and storing the images (and text) in an NSMutableArray.
This sample demonstrates a multi-stage
approach to loading and displaying a
UITableView. It begins by loading the
relevant text from an RSS feed so the
table can load as quickly as possible,
and then downloads the images for each
row asynchronously so the UI is more
responsive.