13

I thought I had it figured out but I just can't get it to work. I have a method that is called on every URL in an array. This method have a URL of a picture that should be downloaded in specific path in a Application Support folder for offline use. But maybe I'm misinterpreting the methods in the AFNetwork library. My method looks like this:

- (void) downloadImageInBackground:(NSDictionary *)args{

  @autoreleasepool {

    NSString *photourl = [args objectForKey:@"photoUrl"];
    NSString *articleID = [args objectForKey:@"articleID"];
    NSString *guideName = [args objectForKey:@"guideName"];
    NSNumber *totalNumberOfImages = [args objectForKey:@"totalNumberOfImages"];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:photourl]];

    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.inputStream = [NSInputStream inputStreamWithURL:[NSURL URLWithString:photourl]];

    [operation setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
        DLog(@"PROBLEMS_AF");
    }];
    DLog(@"URL_PHOTOURL: %@", photourl);
    DLog(@"indexSet: %@", operation.hasAcceptableStatusCode); 
    [operation  response];

    NSData *data = [args objectForKey:@"data"];

    NSString *path;
    path = [NSMutableString stringWithFormat:@"%@/Library/Application Support/Guides", NSHomeDirectory()];
    path = [path stringByAppendingPathComponent:guideName];
    NSString *guidePath = path;
    path = [path stringByAppendingPathComponent:photourl];

    if ([[NSFileManager defaultManager] fileExistsAtPath:guidePath]){
        [[NSFileManager defaultManager] createFileAtPath:path
                                                contents:data
                                              attributes:nil];
    }

    DLog(@"path: %@", path);
    operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
    [operation start];


    DLog(@"isExecuting: %d",[operation isExecuting]);
    DLog(@"IS_FINISHED: %d",[operation isFinished]);


  }

} 

PhotoURL is the direct link to the image that I want to download.

Since this method is called for all the images, all logs is called several times, and seems to be correct.

choise
  • 24,636
  • 19
  • 75
  • 131
Joakim Engstrom
  • 6,243
  • 12
  • 48
  • 67

2 Answers2

40

You have a few problems here. so first, why are you using @autoreleasepool? i think there is no need for this here. also, are you using ARC? i consider this, for the rest of my answer.

in AFNetworking there is a class called AFImageRequestOperation , so this would be a good idea for you to use. first, import it

#import "AFImageRequestOperation.h"

then you could create an object

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:photourl]];
AFImageRequestOperation *operation;
operation = [AFImageRequestOperation imageRequestOperationWithRequest:request 
    imageProcessingBlock:nil 
    cacheName:nil 
    success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {

    } 
    failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        NSLog(@"%@", [error localizedDescription]);
    }];

now, in the success block, you got the UIImage you need. there you need to get the documents directory. your code will not work on an ios device.

// Get dir
NSString *documentsDirectory = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
NSString *pathString = [NSString stringWithFormat:@"%@/%@",documentsDirectory, guideName];

and then you could use NSDatas writeToFile

// Save Image
NSData *imageData = UIImageJPEGRepresentation(image, 90);
[imageData writeToFile:pathString atomically:YES];

at last, you need to start the operation

[operation start];

all together:

- (void)downloadImageInBackground:(NSDictionary *)args{

    NSString *guideName = [args objectForKey:@"guideName"];
    NSString *photourl = [args objectForKey:@"photoUrl"];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:photourl]];

    AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request 
        imageProcessingBlock:nil 
        cacheName:nil 
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {

            // Get dir
            NSString *documentsDirectory = nil;
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            documentsDirectory = [paths objectAtIndex:0];
            NSString *pathString = [NSString stringWithFormat:@"%@/%@",documentsDirectory, guideName];

            // Save Image
            NSData *imageData = UIImageJPEGRepresentation(image, 90);
            [imageData writeToFile:pathString atomically:YES];

        } 
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
            NSLog(@"%@", [error localizedDescription]);
        }];

    [operation start];
}
choise
  • 24,636
  • 19
  • 75
  • 131
  • You pointed me straight. Had some doubts about what I was doing, thank you very much. – Joakim Engstrom Jun 25 '12 at 16:05
  • 2
    additional: if you have multiple requests at once, consider using an `NSOperationQueue` (all AFRequestOperation Classes are coding compliant) and let the queue handle the operations. this could come in handy and will not fire 100 requests at the same time. – choise Jun 25 '12 at 16:29
  • How many requests can an app usually handle without implementing NSOperationQueue?, it will never be more than 50 requests, is that enough that it's worth implementing? – Joakim Engstrom Jun 27 '12 at 10:05
  • yes, if you do these requests simultaneously i would implement it, especially because this are only 3-4 lines of code. – choise Jun 28 '12 at 16:26
  • 1
    @Joakim Engstrom Ist not the app that limits the requests, usually it's the internet connection. Often WAN connection can only handle 1 downloads at a time. It's better trying to keep things in order before trouble happens - like choise mentioned with a queue. You can easily limit this queue with setMaxConcurrentOperationCount:1 – brainray Nov 25 '13 at 14:53
  • 1
    `AFImageRequestOperation` was __deleted__ from `AFNetworking`. Use `AFHTTPRequestOperation` and set its `responseSerializer` to an `AFImageResponseSerializer` – boweidmann Mar 14 '15 at 18:18
5

The problem here is, the operation is not retained. It will be deallocated immediately.

Either make the operation a property of your class or let a operation queue (also a property) handle the request for you (recommended). In the latter case don't call [operation start]. When you use AFHTTPClient the operation queue will also be managed for you.

Also you should register a completion callback for the request operation (setCompletionBlockWithSuccess:failure:).

Felix
  • 35,354
  • 13
  • 96
  • 143