Matt Fellows Matt Fellows - 15 days ago 4
iOS Question

NSURLSessionDownloadTask file removed before reading contents

I have the following piece of code which has been working for several months and has just started becoming intermittently broken.

I am making changes to the way that it is being called, so it's not totally unsurprising, but the symptoms are baffling me. The temporary file being downloaded by some requests, is seemingly being deleted before the data is read.

I have attributes about the file, it's location, and when running in a simulator I've even confirmed the file contains the correct data, while in a breakpoint. However as soon as

[NSData dataWithContentsOfFile:[location path]]
or
[NSData dataWithContentsOfURL:location]
is called, the file is removed from the filesystem and if I use the method with an NSError passed in, the error I get is
Error Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be completed.


My Code:

- (void)makeSoapRequest:(NSURL *)url soapRequest:(NSString *)soapRequest completionBlock:(void (^)(NSData *xmlToParse, int error))completionBlock attemptNumber:(int)attemptNumber
{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:nil delegateQueue:nil];
NSData *bodyData = [soapRequest dataUsingEncoding:NSUTF8StringEncoding];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url];
[request setHTTPMethod:@"POST"];
[request addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"" forHTTPHeaderField:@"SOAPAction"];
[request addValue:[NSString stringWithFormat:@"%d",(int)[bodyData length]] forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:bodyData];

NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (!(location == nil))
{
dispatch_async(dispatch_get_main_queue(), ^{
if (error)
{
NSLog(@"nsurl error was: %@", error);
}
else
{
NSError* errorObj;
NSString *path = [location path];

NSDictionary* attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&errorObj];
NSData *xmlData= [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&errorObj];
if (errorObj != nil) {
NSLog(@"Attributes of file: %@", attribs);
NSLog(@"Error reading the downloaded data, error was: %@", errorObj);
}
completionBlock(xmlData, 0);
return;
}
});
}
else
{
if (error)
{
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(nil,-1);
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(nil,-1);
});
}
}
}];
[downloadTask resume];
}


My Attributes printout:

Attributes of file: {
NSFileCreationDate = "2016-11-23 13:15:00 +0000";
NSFileExtensionHidden = 0;
NSFileGroupOwnerAccountID = 501;
NSFileGroupOwnerAccountName = mobile;
NSFileModificationDate = "2016-11-23 13:15:00 +0000";
NSFileOwnerAccountID = 501;
NSFileOwnerAccountName = mobile;
NSFilePosixPermissions = 384;
NSFileReferenceCount = 1;
NSFileSize = 947;
NSFileSystemFileNumber = 534146;
NSFileSystemNumber = 16777218;
NSFileType = NSFileTypeRegular;
}


Error log:

2016-11-23 13:27:43.285 GCMMobile[2780:829109] Error reading the downloaded data, error was: Error Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be completed. (Cocoa error 260.)" UserInfo=0x1704605c0 {NSFilePath=/private/var/mobile/Containers/Data/Application/BF6BE574-AA1A-492E-A1DD-04334F33E2D7/tmp/CFNetworkDownload_VHyKp6.tmp, NSUnderlyingError=0x17024ce10 "The operation couldn’t be completed. No such file or directory"}


Any help most appreciated...

Answer

The problem is that you are returning from the NSURLSessionDownloadTask completion handler before moving or opening the downloaded file. As the documentation states:

You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.

By dispatching to the main queue and returning you are postponing your file-reading code to some indefinite time in the future. You should move or open the file in the completion handler itself, and only then dispatch to another queue for subsequent processing.