iDev_91
iDev_91

Reputation: 317

Save images from URL to iOS Directory

I want to save a lot (800-2000) images from server to iPhone app directory (Documents directory).

First of all I have to check if those image are already on the iPhone directory.

I use this code to download one image:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg"]];
[NSURLConnection connectionWithRequest:request delegate:self];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:@"pkm.jpg"];
NSData *thedata = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg"]];
[thedata writeToFile:localFilePath atomically:YES];

Please help me, any suggestions: 1. Save all images with the original name on iPhone directory, NOT "pkm.jpg" 2. Save images with names: from 05052008321_bigger to 05052008350_bigger 3. Check if images are downloaded already, DON'T Download again.

I know, maybe this is more than a question. But some suggestions, directions will be really good.

Thanks in advance.

Upvotes: 2

Views: 4991

Answers (2)

Rob
Rob

Reputation: 438212

A couple of reactions:

  1. You're initiating a NSURLConnection connectionWithRequest (which triggers the NSURLConnectionDataDelegate methods), but you then apparently disregard that and initiate a dataWithContentsOfURL. You should pick one or the other, but don't do both.

  2. I'd suggest you pursue a NSOperation-based solution because you'll definitely want to (a) enjoy concurrency; but (b) limit the concurrent operations to some reasonable number (say 4 or 5), otherwise you'll have requests timing out and failing.

  3. In terms of getting the filename, you can retrieve the filename using lastPathComponent from the NSURL. (My download operation, used below, actually will automatically use this if you don't provide an explicit filename.)

  4. You haven't illustrated how you're determining the list of filenames from the remote server, so we'd have to see how you know what images there are to retrieve.

  5. If this is for your own purposes, this is fine, but I've heard claims that Apple rejects apps that request too much data over a cellular connection (and 2000 images would certainly qualify). Frankly, even if Apple doesn't raise a fuss, you really should be asking the user before using that much of their data plan. You can use Reachability to determine whether the user is connecting via wifi or via cellular, and if the latter, you may want to present a warning.

But I'd suggest something that looks like (assuming you have some NSArray with NSString versions of the URL's ... obviously adjust this for whatever form your list of URLs is in):

NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 4;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

for (NSString *urlString in urlStrings)
{
    NSURL *url = [NSURL URLWithString:urlString];
    NSString *path = [docsPath stringByAppendingPathComponent:[url lastPathComponent]];
    if (![fileManager fileExistsAtPath:path]) {
        DownloadOperation *downloadOperation = [[DownloadOperation alloc] initWithURL:url];
        downloadOperation.downloadCompletionBlock = ^(DownloadOperation *operation, BOOL success, NSError *error) {
            if (error) NSLog(@"download of %@ failed: %@", operation.url, error);
        };
        [downloadQueue addOperation:downloadOperation];
    }
}

And that download operation might be something like this. Obviously, use whatever NSOperation based downloader you want, but I'd suggest you use one that doesn't load the whole download into memory, but rather one that streams it directly to persistent storage.

If you don't want to get that fancy, you could just do something like:

NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 4;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

for (NSString *urlString in urlStrings)
{
    NSURL *url = [NSURL URLWithString:urlString];
    NSString *path = [docsPath stringByAppendingPathComponent:[url lastPathComponent]];
    if (![fileManager fileExistsAtPath:path]) {
        [downloadQueue addOperationWithBlock:^{
            NSString *path = [docsPath stringByAppendingPathComponent:[url lastPathComponent]];
            NSData *data = [NSData dataWithContentsOfURL:url];
            if (data)
                [data writeToFile:path atomically:YES];
        }];
    }
}

Clearly, use whatever download operation class you want for downloading, but hopefully you get the basic idea. Create a NSOperation-based download, and then submit one for every file that needs to get downloaded.

Upvotes: 2

Ethan Mick
Ethan Mick

Reputation: 9577

I'm not sure the best method to serialize the information (naively, you could just write the NSDictionary to the disk). I would have a large NSDictionary (which could be broken up into smaller ones, up to you how to do that). The NSDictionary would take the image name "05052008321_bigger" and map it to a simple @YES.

When the app is in a position to download a new image, it would read the NSDictionary from disk (can be read in a different thread), and check if the image name is in the dictionary. This allows lookups to be fast.

- (BOOL)checkIfImageHasBeenDownloaded:(NSString *)imageName
{
    return [self.dictionaryNames objectForKey:imageName] != nil;
}

Upvotes: 1

Related Questions