I want to download a lot of pictures from the server. How can I do this as fast as possible? Currently I am using:
UIImage* myImage = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString: #"http://example.com/image.jpg"]]];
It is painfully slow. Is there any speed increase in downloading multiple images at the same time (asynchronously), and if so how many is too many?
You won't get a definitive answer to the optimal number of connections, because there is none. It just depends on several variables such as bandwidth, image size or your own patience. You need to measure this by yourself to get it right.
Doing asynchronous requests won't increase the downloading speed, but the user experience is way better. Seriously, you should consider doing it for any download that takes more than a second.
I always recommend using ASIHTTPRequest, it makes implementing things such as queues and progress bars easy.
Here's the simplest example from an asynchronous request using the library:
- (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];
}
Update: This library will no longer be supported. From 1:
"Please note that I am no longer working on this library - you may
want to consider using something else for new projects. :)"
Nowadays I use AFNetworking for most of my projects.
Related
I'm using this line to get the contents of a URL:
NSString *result=[[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:URL]
encoding:NSUTF8StringEncoding
error:nil];
The problem is when there's a bad connection, it loads the contents from cache. Is there a way to avoid this behaviour? For example, clearing the cache or something.
First, it's not recommended to use initWithContentsOfURL:encoding:error to load data from a network resource.
Second, if you want to control caching behavior, you should be using an NSURLRequest. NSURLRequest allows you to customize the caching behavior of the request by setting the cachePolicy of the request. In your case, you want to use NSURLRequestReloadIgnoringLocalCacheData. An example of doing this synchronously using NSURLConnection would be:
NSString *result = nil;
NSData *data = nil;
NSURLResponse *response = nil;
NSURLError *error = nil;
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20L];
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (response != nil && [data length] > 0){
result = [NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
Note that this is a very naive implementation that does not check the HTTP status code returned, the mime-type of the response, or perform any error handling. It is also not a recommended practice to load network resources synchronously or to do so from the main thread. A better implementation would use sendAsynchronousRequest:completion: or NSURLSession.
However, it does demonstrate at a high level what you would need to do to answer your question: The NSURLRequest specifies that this request should never use the local cache, and the returned data is used to create an instance of NSString.
Simple cache-buster dummy parameter with random value added to URL should work.
And as #Josh-Caswell said, use NSURLRequest. Although in case of proxy servers, just using NSURLRequest may not help and you will still need cache-buster.
I want to download a file with pause/resume functionality. I read apple documents, there I got NSUrldownload which supports the same but it is not available for iOS. I was trying with NSUrlconnection, but not working. I don't want to use any third party libraries, I want to implement it by myself, below is the code snippet which I tried.
NSString *fileName = [NSString stringWithFormat:#"~%#",[[url componentsSeparatedByString:#"/"] lastObject]];
int dataLength = [[self checkDocDirectoryforFileName:fileName] length];
//dataLength = 0;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setValue:#"audio/mpeg" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"bytes" forHTTPHeaderField:#"Accept-Ranges"];
[request setValue:#"Keep-Alive" forHTTPHeaderField:#"Connection"];
[request setValue:[NSString stringWithFormat:#"%d",dataLength] forHTTPHeaderField:#"Content-Length"];
NSLog(#"Request header %#", [request allHTTPHeaderFields]);
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
Please check it out this:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLDownload.html#//apple_ref/doc/uid/20001839-SW2
Hope, May it will help you,
:)
iOS 7.0 and above
NSURLSession especially NSURLSessionDownloadTask provides this functionality.
The NSURLSession API provides status and progress properties, in
addition to delivering this information to delegates. It supports
canceling, restarting or resuming, and suspending tasks, and it
provides the ability to resume suspended, canceled, or failed
downloads where they left off.
Take a look to the docs.
iOS 5.0 and above
I would use AFDownloadRequestOperation for this. Take a look at this thread.
AFDownloadRequestOperation has
additional support to resume a partial download, uses a temporary
directory and has a special block that helps with calculating the
correct download progress.
I'm currently doing this when populating core data from a JSON file:
NSString *urlString = [value objectForKey:#"url"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *dataResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[managedObject setValue:dataResponse forKey:#"image"];
Is there a better (asynchronous) way to do this with AFNetworking? What is the best method for this case? Does it have to be synchronous because we're dealing with CoreData?
UPDATE: Trying this now:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
[managedObject setValue:data forKey:#"image"];
}];
For some reason when I access the managed object later, the image attribute is always null, even though *data above is not null in the completion handler. The image gets saved fine in the synchronous method. What am I missing?
NSURLConnection can deal with async too.
The method that you can use is (iOS >= 5) is
+ sendAsynchronousRequest:queue:completionHandler:
If you need to target iOS < 5 then use the delegate pattern for NSURLConnection. A good wrapper for this can be found in NSURLConnection and grand central dispatch.
About Core Data, I would say it depends. If data you need to store is cheap, do it in the main thread. On the contrary you have three different ways to do it:
(1) use new Core Data queue-based API (iOS >= 5)
(2) kick off a NSOperation within a NSOperationQueue and do the long work in background
(3) use GDC
Pay attention to Core Data constraints (threads constraints) when you deal with (2) or (3).
Hope that helps.
P.S. If you want to know something else let me know.
There's a sendAsynchronousRequest:queue:completionHandler: message of NSURLConnection.
I am using ASIHTTPRequest framework, in the document, what are the differences between the 2nd and 3rd example, in usage, advantage and disadvantage?
2nd example (Creating an asynchronous request):
- (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];
}
3rd example (Using blocks)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
The blocks in iOS are a part of Concurrent Programming
You use a block when you want to create units of work (that is, code segments) that can be passed around as though they are values. Blocks are usually used for writting a callbacks.
Usually, using blocks do not reflect in different applicatino behaviour. The syntactical difference is that, when using blocks you do not need to define a request delegate or implement delegate methods (such as -requestFinished: and -requestFailed:) for async requests.
One of the advantages is in accessing local method variables in completion block, bacause the function expression in block can reference and can preserve access to local variables (like variable url in your method -grabURLInBackground: or any other local variable defined in your method).
The second adventage is in using nested request calls. For example, you may need to perform a few requests in sequence, and without blocks you will need to define a separate delegate method callback for each request, which may result in reduced readability of your code.
Blocks allow you to write code at the point of invocation that is executed later in the context of the method implementation, which may be very usefull, when you get used to using them.
Some patterns to avoid when using blocks are mentioned in Apple Blocks Programming Topis
I have URL like this: http://example.com/image.jpg
What the simplest way to save url's target content into the local file on the iOS (iPhone)?
One of the simplest way is the following:
NSURL *url = [NSURL URLWithString:..];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *fileName = ..
[data writeToFile:fileName atomically:NO];
A simple way to do this would be to use the ASIHTTPRequest project. Primarily because it already has the required reachability checks built in and it is easy to setup and use asynchronous request.
Asynchronous download example from the site:
- (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 binary data
NSData *responseData = [request responseData];
}
Reachability comment from the site:
It allows ASIHTTPRequest to be
notified when the network connection
changes from WWAN to WiFi, or
vice-versa.