Good morning, I'm developing an application that uploads some files; there are no real issues, but only one big problem: if I try to upload 1gb of files it allocates 1gb of memory, and you can understand this is not a good thing.
Here's my code: I allocate the data as NSData and then I use NSMutableURLRequest + NSURLConnection to upload them; my question is: are there any ways to do this without needing to allocate whole memory? I've searched out, but I found nothing...
_uploadDataFile is a NSData sub-class instance allocated before.
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://my.server/page"]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0] autorelease];
NSData *dataFile;
if (_uploadDataFile == nil) {
NSError *error = nil;
dataFile = [[[NSData alloc] initWithContentsOfFile:@"pathToFile" options:NSDataReadingUncached error:&error] autorelease];
if (error) {
NSLog(@"Error %i: %@", [error code], [error localizedDescription]);
[self fermaUpload]; //Stop Upload
return;
}
}
else
dataFile = [_uploadDataFile file];
NSMutableDictionary *dictPost = [[[NSMutableDictionary alloc] init] autorelease];
[dictPost setValue:dataFile forKey:@"uploads[]"];
NSData *mutData = [self dataForPOSTWithDictionary:dictPost boundary:@"0194784892923" nomeFile:[@"pathToFile" lastPathComponent]];
NSString *postLength = [NSString stringWithFormat:@"%d", [mutData length]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"multipart/form-data; boundary=0194784892923" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:mutData];
[request setHTTPShouldHandleCookies:YES];
theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
and here's how - (NSData *)dataForPOSTWithDictionary:boundary:nomeFile: method looks like:
- (NSData *)dataForPOSTWithDictionary:(NSDictionary *)aDictionary boundary:(NSString *)aBoundary nomeFile:(NSString *)nomeFile {
NSArray *myDictKeys = [aDictionary allKeys];
NSMutableData *myData = [NSMutableData dataWithCapacity:1];
NSString *myBoundary = [NSString stringWithFormat:@"--%@\r\n", aBoundary];
for(int i = 0;i < [myDictKeys count];i++) {
id myValue = [aDictionary valueForKey:[myDictKeys objectAtIndex:i]];
[myData appendData:[myBoundary dataUsingEncoding:NSUTF8StringEncoding]];
//if ([myValue class] == [NSString class]) {
if ([myValue isKindOfClass:[NSString class]]) {
[myData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", [myDictKeys objectAtIndex:i]] dataUsingEncoding:NSUTF8StringEncoding]];
[myData appendData:[[NSString stringWithFormat:@"%@", myValue] dataUsingEncoding:NSUTF8StringEncoding]];
} else if(([myValue isKindOfClass:[NSURL class]]) && ([myValue isFileURL])) {
[myData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [myDictKeys objectAtIndex:i], [[myValue path] lastPathComponent]] dataUsingEncoding:NSUTF8StringEncoding]];
[myData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[myData appendData:[NSData dataWithContentsOfFile:[myValue path]]];
} else if(([myValue isKindOfClass:[NSData class]])) {
[myData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [myDictKeys objectAtIndex:i], nomeFile] dataUsingEncoding:NSUTF8StringEncoding]];
[myData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[myData appendData:myValue];
} // eof if()
[myData appendData:[[NSString stringWithString:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
} // eof for()
[myData appendData:[[NSString stringWithFormat:@"--%@--\r\n", aBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
return myData; } // eof dataForPOSTWithDictionary:boundary:
Thank you and sorry for my english.
A most interesting problem! There are two ways of feeding body data into NSURLConnection/Request
:
NSData
NSInputStream
Unfortunately, neither does what you want out of the box, since you want to upload a file + some form data. Ideally, you could subclass NSInputStream
to provide the data formatted as you require. However, the last time I checked, NSURLConnection
is unable to handle such subclasses. Instead, do something like:
CFStreamCreateBoundPair()
. This returns a CFWriteStream
and CFReadStream
, which are toll-free bridged to NSOutputStream
and NSInputStream
, respectively-setHTTPBodyStream:
on the URL request with the input streamThe output stream's delegate should now be called periodically, requesting data. You need to feed in the POST data, followed by chunks of file data. Thus the entire file is never entirely in memory at once.