更新iOS10后发现用backgroundSession进行下载时,请求暂停后再继续下载会出错,我们项目里报的ErrorCode = -3003(NSURLErrorCannotWriteToFile)。
backgroundSession初始化方式如下
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
[toPauseDownloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
[self saveResumeData:resumeData forURL:aDownLoadObject.url];
}];
NSData *resumeData = [self getResumeDataForURL:toDownLoadUrl];
if (resumeData != nil) {
currentDownLoadTask = [_currentSession downloadTaskWithResumeData:resumeData];
}
[currentDownLoadTask resume];
然后会收到错误回调- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error ,ErrorCode = -3003(NSURLErrorCannotWriteToFile)。
有人在developer提出了该问题,已经有大神在StackOverflow上提出了解决办法,由于给出的代码是swift版本的,我们项目还是用的OC故写了一个OC版本。
问题出现的原因是“This problem arose from currentRequest and originalRequest NSKeyArchived encoded with an unusual root of "NSKeyedArchiveRootObjectKey" instead of NSKeyedArchiveRootObjectKey constant which is "root" literally and some other misbehaves in encoding process of NSURL(Mutable)Request.”
解决办法是在iOS10时将resumeData转换成正确格式的数据。给NSURLSessin添加类别实现correctedDownloadTaskWithResumeData:方法,使用该扩展方法从u rlSession获取downloadTask:
NSData *resumeData = [self getResumeDataForURL:toDownLoadUrl];
if (resumeData != nil) {
currentDownLoadTask = [_currentSession correctedDownloadTaskWithResumeData:resumeData];
}
[currentDownLoadTask resume];
Categary文件源码如下(Objective-C):
// NSURLSession+DQCorrectedResumeData.h
//
// Created by 刘洋 on 16/9/27.
// Copyright © 2016年 liuyang635. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSURLSession (DQCorrectedResumeData)
- (NSURLSessionDownloadTask *)correctedDownloadTaskWithResumeData:(NSData *)resumeData;
@end
// NSURLSession+DQCorrectedResumeData.m
//
// Created by 刘洋 on 16/9/27.
// Copyright © 2016年 liuyang625. All rights reserved.
//
#import "NSURLSession+DQCorrectedResumeData.h"
@implementation NSURLSession (DQCorrectedResumeData)
NSData * correctRequestData(NSData *data) {
if (!data) {
return nil;
}
// return the same data if it's correct
if ([NSKeyedUnarchiver unarchiveObjectWithData:data] != nil) {
return data;
}
NSMutableDictionary *archive = [[NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil] mutableCopy];
if (!archive) {
return nil;
}
NSInteger k = 0;
id objectss = archive[@"$objects"];
while ([objectss[1] objectForKey:[NSString stringWithFormat:@"$%ld",k]] != nil) {
k += 1;
}
NSInteger i = 0;
while ([archive[@"$objects"][1] objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]] != nil) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
[dic removeObjectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[@"$objects"] = arr;
}
i++;
}
if ([archive[@"$objects"][1] objectForKey:@"__nsurlrequest_proto_props"] != nil) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:@"__nsurlrequest_proto_props"];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
[dic removeObjectForKey:@"__nsurlrequest_proto_props"];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[@"$objects"] = arr;
}
}
// Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root"
if ([archive[@"$top"] objectForKey:@"NSKeyedArchiveRootObjectKey"] != nil) {
[archive[@"$top"] setObject:archive[@"$top"][@"NSKeyedArchiveRootObjectKey"] forKey: NSKeyedArchiveRootObjectKey];
[archive[@"$top"] removeObjectForKey:@"NSKeyedArchiveRootObjectKey"];
}
// Reencode archived object
NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
return result;
}
NSMutableDictionary *getResumeDictionary(NSData *data) {
NSMutableDictionary *iresumeDictionary = nil;
if ([[UIDevice currentDevice] systemVersion].floatValue >= 10.0) {
id root = nil;
id keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
@try {
root = [keyedUnarchiver decodeTopLevelObjectForKey:@"NSKeyedArchiveRootObjectKey" error:nil];
if (root == nil) {
root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:nil];
}
} @catch(NSException *exception) {
}
[keyedUnarchiver finishDecoding];
iresumeDictionary = [root mutableCopy];
}
if (iresumeDictionary == nil) {
iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil];
}
return iresumeDictionary;
}
NSData *correctResumeData(NSData *data) {
NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
if (data == nil) {
return nil;
}
NSMutableDictionary *resumeDictionary = getResumeDictionary(data);
if (resumeDictionary == nil) {
return nil;
}
resumeDictionary[kResumeCurrentRequest] = correctRequestData(resumeDictionary[kResumeCurrentRequest]);
resumeDictionary[kResumeOriginalRequest] = correctRequestData(resumeDictionary[kResumeOriginalRequest]);
NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
return result;
}
- (NSURLSessionDownloadTask *)correctedDownloadTaskWithResumeData:(NSData *)resumeData {
NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
NSData *cData = correctResumeData(resumeData);
cData = cData?cData:resumeData;
NSURLSessionDownloadTask *task = [self downloadTaskWithResumeData:cData];
NSMutableDictionary *resumeDic = getResumeDictionary(cData);
if (resumeDic) {
if (task.originalRequest == nil) {
NSData *originalReqData = resumeDic[kResumeOriginalRequest];
NSURLRequest *originalRequest = [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData ];
if (originalRequest) {
[task setValue:originalRequest forKey:@"originalRequest"];
}
}
if (task.currentRequest == nil) {
NSData *currentReqData = resumeDic[kResumeCurrentRequest];
NSURLRequest *currentRequest = [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData];
if (currentRequest) {
[task setValue:currentRequest forKey:@"currentRequest"];
}
}
}
return task;
}
@end