2

I am trying to download multiple images from a URL stored in an XML feed. Getting the image urls from the XML is working correctly. However, the NSURLConnection is creating empty files, but the data is received as noted in NSLog. In connectionDidFinishLoading:(NSURLConnection *)connection, the data and correct bytes are received, the problem is how do I make the receivedData write to the correct file.

Semi-working code:

-(void)parsingComplete:(XMLDataSource*)theParser 
{
    /*  iterate through the Categories and create the 
        sub-directory if it does not exist  
     */
    for (int i = 0; i < [categories count]; i++) {
        NSString *cat      = [NSString stringWithFormat:@"%@/%@",BASE_DIR,[[categories objectAtIndex:i] objectForKey:@"name"]];
        NSString *catName  = [[categories objectAtIndex:i] objectForKey:@"name"];
        NSArray  *catArray = [[categories objectAtIndex:i] objectForKey:@"images"];

        /*  create the sub-direcotry naming it the #category# key  */
        if (![FILEMANAGER fileExistsAtPath:cat]) {
            [FILEMANAGER createDirectoryAtPath:cat withIntermediateDirectories:NO attributes:nil error:nil];
        }

        //NSLog(@"\n\nCategory: %@",cat);
        for (int x = 0; x < [catArray count]; x++) {
            //NSLog(@"Image: %@",[[catArray objectAtIndex:x] objectForKey:@"imageUrl"]);   
            /*  download each file to the corresponding category sub-directory  */
            fileOut = [NSString stringWithFormat:@"%@/%@_0%i.jpg",cat,catName,x];

            NSURLRequest *imageRequest = 
            [NSURLRequest requestWithURL:[NSURL URLWithString:[[catArray objectAtIndex:x] objectForKey:@"imageUrl"]]
                             cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.0];
            NSURLConnection *imageConnection = [[NSURLConnection alloc] initWithRequest:imageRequest delegate:self];

            int counter = 0;
            //BOOL result = NO;
            if(imageConnection)
            {
                NSLog(@"Counter: %i",counter++);
                receivedData = [[NSMutableData data] retain];
                /*result = */[receivedData writeToFile:fileOut atomically:YES];
            }
            /*
                if (!result) NSLog(@"Failed"); else NSLog(@"Successful");
             */
        }
    }
}

#pragma mark NSURLConenction

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response  {  
    [receivedData setLength:0];  
} 
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data  {  
    [receivedData appendData:data];
}  
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // release the connection, and the data object
    [connection release];
    // receivedData is declared as a method instance elsewhere
    [receivedData release];
    // inform the user
    NSLog(@"Connection failed! Error - %@ %@",
          [error localizedDescription],
          [[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection  
{  
    // do something with the data  
    // receivedData is declared as a method instance elsewhere  
    NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);  
    NSString *aStr = [[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding];  
    // release the connection, and the data object  
    //[receivedData release];  
} 
WrightsCS
  • 50,551
  • 22
  • 134
  • 186
  • On iOS I do the following on the connectionDidFinishLoading: I create a NSXMLParser with the received data and then call [parser parse]. I also need to Implement NSXMLParserDelegate methods, hope this helps – Sarah May 17 '11 at 06:44

2 Answers2

3

You have to wait until the connection tells you it has finished before you can write the data. The connection is handled on another thread; if you try to access the data immediately on the original thread as you're doing, there won't be anything in it.

You should move the writeToFile: call to the end of connectionDidFinishLoading:, or to another method that you call from there. That's the first point where you know that the data has all been collected.

I'd also suggest creating the NSMutableData instance in didRecieveResponse:, so that you know that it is available at the correct time. That will be more readable/understandable. You can think of the delegate methods as a collective "scope" -- the data is only used inside of them, so it should be created inside of one of them.

In reply to your comment:

One possibility, since you have so much that needs to be done around this one download, and don't seem to be touching the GUI, is to run the whole parsingComplete: method on a background thread, and using +[NSURLConnection sendSynchronousRequest:returningResponse:error:]. This way your code will just wait until the data comes back, in one piece, and you can write it immediately after the sendSynchronous... call returns.

NSError * err;
NSURLResponse * response;
NSData * receivedData = [NSURLConnection sendSynchronousRequest:imageRequest
                                              returningResponse:&response
                                                          error:&err];
if( !receivedData ){
    /* Handle error */
}
/* Check response */

BOOL result = [receivedData writeToFile:fileOut atomically:YES];
/* check result, etc. */
jscs
  • 63,694
  • 13
  • 151
  • 195
  • I know that `writeToFile:` works in `connectionDidFinishLoading `, but I need to follow a directory structure and name each file in sequential order, **`fileOut = [NSString stringWithFormat:@"%@/%@_0%i.jpg",cat,catName,x];`**, but that format will not work in `connectionDidFinishLoading` unfortunately. – WrightsCS May 17 '11 at 06:56
  • 1
    Updated with another suggestion. – jscs May 17 '11 at 07:01
  • Actually, I just moved the entire `for` statements to `connectionDidFinishLoading`, and that did it just fine, thanks for the suggestion. – WrightsCS May 17 '11 at 07:01
  • Glad to help! It's funny, I got into a big discussion about not using the synchronous mode a week or two ago, and here I am recommending it. Right tool for the job, and all that. – jscs May 17 '11 at 07:07
  • Now I have a new issue .. the files seem to overwrite each other, and at the end of the complete download, all of the images are the same!! – WrightsCS May 17 '11 at 07:38
  • Wait...which part of the `for` loop did you move to `connectionDidFinishLoading:`? If you're looping through the list of names each time you get a complete data object, you're going to write that same data out for each name, and at the end of the whole process, every file will just have the data from the final connection. – jscs May 17 '11 at 07:42
  • @Josh http://stackoverflow.com/questions/6027810/downloading-multiple-files-writes-same-data-to-all-files – WrightsCS May 17 '11 at 07:49
1

You can use a CustomURLConnection with Tag to name de images before they download.

With this code you can make a customURLConnection, name it when you make the request, and ask for the name of the image in the connectionDidFinishLoading:

CustomURLConnection.h

#import <Foundation/Foundation.h>

@interface CustomURLConnection : NSURLConnection 
{
NSString *tag;
}

@property (nonatomic, retain) NSString *tag;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)aTag;

@end

CustomURLConnection.m

#import "CustomURLConnection.h"

@implementation CustomURLConnection

@synthesize tag;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)aTag 
{
self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];

    if (self) {
        self.tag = aTag;
    }
    return self;
}

- (void)dealloc 
{
    [tag release];
    [super dealloc];
}

@end

Then make the connection, a custom url connection in your parsingComplete with:

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:yourURL];
[request setTimeoutInterval:3000.0];

CustomURLConnection *connection = [[CustomURLConnection alloc]     initWithRequest:request delegate:self startImmediately:YES tag:imageTag];

Now you can take the imageName with the CustomURLConnection tag, and save it in the connectionDidFinishLoading:

CustomURLConnection *urlConec = (CustomURLConnection*)connection;

NSMutableData *dataFromConnection = [self dataForConnection:urlConec];

and this is the code for the function dataForConnection:

- (NSMutableData*)dataForConnection:(CustomURLConnection*)connection 
{
    NSMutableData *data = [receivedData objectForKey:connection.tag];
    return data;
}

Hope that helps.

WrightsCS
  • 50,551
  • 22
  • 134
  • 186
Luis Ascorbe
  • 1,985
  • 1
  • 22
  • 36