iOS 10将很快发布,因此值得测试应用程序与它的兼容性。在这样的测试中,我们发现我们的应用程序不能在iOS10上恢复后台下载。在早期版本上工作良好的代码不适用于新版本,无论是在模拟器上还是在设备上。
我没有将代码简化为最小的工作测试用例,而是搜索了NSUrlSession教程并对其进行了测试。行为是一样的:恢复以前版本的iOS,但在第10次中断。
复制步骤:
预期成果:
继续下载。您可以在iOS10之前检查它如何与版本一起工作。
实际结果:
下载失败。在xcode控制台中可以看到:
2016-09-02 16:11:24.913 HalfTunes[35205:2279228] *** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
2016-09-02 16:11:24.913 HalfTunes[35205:2279228] *** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
2016-09-02 16:11:24.913 HalfTunes[35205:2279228] Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file.
更多情景:
如果在下载文件时激活脱机模式,则
Url session completed with error: Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL} {
NSLocalizedDescription = "unsupported URL";
}
当网络关闭并下载时,永远不要在网络再次启动时恢复。其他带有暂停的用例,例如重新启动,也不起作用。
补充调查:
中建议的代码检查返回的resumeData是否有效
How can I check that an NSData blob is valid as resumeData for an NSURLSessionDownloadTask?
但目标文件已经就位。尽管resumeData格式已经更改,现在文件名存储在NSURLSessionResumeInfoTempFileName中,但您必须将NSTemporaryDirectory()附加到其中。
除此之外,我还向苹果填写了一份错误报告,但他们还没有回复。
(关于生命、宇宙和一切的)问题:
NSUrlSession的恢复是否在所有其他应用程序中都被破坏了?它能固定在应用程序端吗?
发布于 2016-09-06 11:03:15
这个问题产生于currentRequest和originalRequest NSKeyArchived,它们用" NSKeyedArchiveRootObjectKey“这个不寻常的词根来编码,而不是NSKeyedArchiveRootObjectKey常量(即”根“),以及在NSURL(可变的)请求的编码过程中的其他一些错误行为。
我在beta 1中检测到了这一点,并提交了一个bug (编号27144153,以防您想要重复)。甚至我也发了一封电子邮件给"Quinn“(苹果网站上的eskimo1),他是NSURLSession团队的支持者,以确认他们收到了这封邮件,他说他们收到了这封邮件,并且意识到了问题的存在。
更新:我终于想出了解决这个问题的方法。将数据提供给correctResumeData()函数,它将返回可用的简历数据
NSURLSession.correctedDownloadTaskWithResumeData() / URLSession.correctedDownloadTask(withResumeData:)函数可以使用/函数获得具有正确的originalRequest和currentRequest变量的任务
更新3: Quinn说这个问题在iOS 10.2中得到了解决,您可以继续使用该代码来与iOS 10.0和10.1兼容,并且它可以与新版本兼容,不会出现任何问题。
(对于Swift 3代码,在下面滚动,目标C参见leavesstar post,但我没有测试它)
Swift 2.3:
func correctRequestData(data: NSData?) -> NSData? {
guard let data = data else {
return nil
}
// return the same data if it's correct
if NSKeyedUnarchiver.unarchiveObjectWithData(data) != nil {
return data
}
guard let archive = (try? NSPropertyListSerialization.propertyListWithData(data, options: [.MutableContainersAndLeaves], format: nil)) as? NSMutableDictionary else {
return nil
}
// Rectify weird __nsurlrequest_proto_props objects to $number pattern
var k = 0
while archive["$objects"]?[1].objectForKey("$\(k)") != nil {
k += 1
}
var i = 0
while archive["$objects"]?[1].objectForKey("__nsurlrequest_proto_prop_obj_\(i)") != nil {
let arr = archive["$objects"] as? NSMutableArray
if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_prop_obj_\(i)"] {
dic.setObject(obj, forKey: "$\(i + k)")
dic.removeObjectForKey("__nsurlrequest_proto_prop_obj_\(i)")
arr?[1] = dic
archive["$objects"] = arr
}
i += 1
}
if archive["$objects"]?[1].objectForKey("__nsurlrequest_proto_props") != nil {
let arr = archive["$objects"] as? NSMutableArray
if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_props"] {
dic.setObject(obj, forKey: "$\(i + k)")
dic.removeObjectForKey("__nsurlrequest_proto_props")
arr?[1] = 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
let result = try? NSPropertyListSerialization.dataWithPropertyList(archive, format: NSPropertyListFormat.BinaryFormat_v1_0, options: NSPropertyListWriteOptions())
return result
}
func getResumeDictionary(data: NSData) -> NSMutableDictionary? {
var iresumeDictionary: NSMutableDictionary? = nil
// In beta versions, resumeData is NSKeyedArchive encoded instead of plist
if #available(iOS 10.0, OSX 10.12, *) {
var root : AnyObject? = nil
let keyedUnarchiver = NSKeyedUnarchiver(forReadingWithData: data)
do {
root = try keyedUnarchiver.decodeTopLevelObjectForKey("NSKeyedArchiveRootObjectKey") ?? nil
if root == nil {
root = try keyedUnarchiver.decodeTopLevelObjectForKey(NSKeyedArchiveRootObjectKey)
}
} catch {}
keyedUnarchiver.finishDecoding()
iresumeDictionary = root as? NSMutableDictionary
}
if iresumeDictionary == nil {
do {
iresumeDictionary = try NSPropertyListSerialization.propertyListWithData(data, options: [.MutableContainersAndLeaves], format: nil) as? NSMutableDictionary;
} catch {}
}
return iresumeDictionary
}
func correctResumeData(data: NSData?) -> NSData? {
let kResumeCurrentRequest = "NSURLSessionResumeCurrentRequest"
let kResumeOriginalRequest = "NSURLSessionResumeOriginalRequest"
guard let data = data, let resumeDictionary = getResumeDictionary(data) else {
return nil
}
resumeDictionary[kResumeCurrentRequest] = correctRequestData(resumeDictionary[kResumeCurrentRequest] as? NSData)
resumeDictionary[kResumeOriginalRequest] = correctRequestData(resumeDictionary[kResumeOriginalRequest] as? NSData)
let result = try? NSPropertyListSerialization.dataWithPropertyList(resumeDictionary, format: NSPropertyListFormat.XMLFormat_v1_0, options: NSPropertyListWriteOptions())
return result
}
extension NSURLSession {
func correctedDownloadTaskWithResumeData(resumeData: NSData) -> NSURLSessionDownloadTask {
let kResumeCurrentRequest = "NSURLSessionResumeCurrentRequest"
let kResumeOriginalRequest = "NSURLSessionResumeOriginalRequest"
let cData = correctResumeData(resumeData) ?? resumeData
let task = self.downloadTaskWithResumeData(cData)
// a compensation for inability to set task requests in CFNetwork.
// While you still get -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL error,
// this section will set them to real objects
if let resumeDic = getResumeDictionary(cData) {
if task.originalRequest == nil, let originalReqData = resumeDic[kResumeOriginalRequest] as? NSData, let originalRequest = NSKeyedUnarchiver.unarchiveObjectWithData(originalReqData) as? NSURLRequest {
task.setValue(originalRequest, forKey: "originalRequest")
}
if task.currentRequest == nil, let currentReqData = resumeDic[kResumeCurrentRequest] as? NSData, let currentRequest = NSKeyedUnarchiver.unarchiveObjectWithData(currentReqData) as? NSURLRequest {
task.setValue(currentRequest, forKey: "currentRequest")
}
}
return task
}
}
Swift 3:
func correct(requestData data: Data?) -> Data? {
guard let data = data else {
return nil
}
if NSKeyedUnarchiver.unarchiveObject(with: data) != nil {
return data
}
guard let archive = (try? PropertyListSerialization.propertyList(from: data, options: [.mutableContainersAndLeaves], format: nil)) as? NSMutableDictionary else {
return nil
}
// Rectify weird __nsurlrequest_proto_props objects to $number pattern
var k = 0
while ((archive["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "$\(k)") != nil {
k += 1
}
var i = 0
while ((archive["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "__nsurlrequest_proto_prop_obj_\(i)") != nil {
let arr = archive["$objects"] as? NSMutableArray
if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_prop_obj_\(i)"] {
dic.setObject(obj, forKey: "$\(i + k)" as NSString)
dic.removeObject(forKey: "__nsurlrequest_proto_prop_obj_\(i)")
arr?[1] = dic
archive["$objects"] = arr
}
i += 1
}
if ((archive["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "__nsurlrequest_proto_props") != nil {
let arr = archive["$objects"] as? NSMutableArray
if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_props"] {
dic.setObject(obj, forKey: "$\(i + k)" as NSString)
dic.removeObject(forKey: "__nsurlrequest_proto_props")
arr?[1] = dic
archive["$objects"] = arr
}
}
/* I think we have no reason to keep this section in effect
for item in (archive["$objects"] as? NSMutableArray) ?? [] {
if let cls = item as? NSMutableDictionary, cls["$classname"] as? NSString == "NSURLRequest" {
cls["$classname"] = NSString(string: "NSMutableURLRequest")
(cls["$classes"] as? NSMutableArray)?.insert(NSString(string: "NSMutableURLRequest"), at: 0)
}
}*/
// Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root"
if let obj = (archive["$top"] as? NSMutableDictionary)?.object(forKey: "NSKeyedArchiveRootObjectKey") as AnyObject? {
(archive["$top"] as? NSMutableDictionary)?.setObject(obj, forKey: NSKeyedArchiveRootObjectKey as NSString)
(archive["$top"] as? NSMutableDictionary)?.removeObject(forKey: "NSKeyedArchiveRootObjectKey")
}
// Reencode archived object
let result = try? PropertyListSerialization.data(fromPropertyList: archive, format: PropertyListSerialization.PropertyListFormat.binary, options: PropertyListSerialization.WriteOptions())
return result
}
func getResumeDictionary(_ data: Data) -> NSMutableDictionary? {
// In beta versions, resumeData is NSKeyedArchive encoded instead of plist
var iresumeDictionary: NSMutableDictionary? = nil
if #available(iOS 10.0, OSX 10.12, *) {
var root : AnyObject? = nil
let keyedUnarchiver = NSKeyedUnarchiver(forReadingWith: data)
do {
root = try keyedUnarchiver.decodeTopLevelObject(forKey: "NSKeyedArchiveRootObjectKey") ?? nil
if root == nil {
root = try keyedUnarchiver.decodeTopLevelObject(forKey: NSKeyedArchiveRootObjectKey)
}
} catch {}
keyedUnarchiver.finishDecoding()
iresumeDictionary = root as? NSMutableDictionary
}
if iresumeDictionary == nil {
do {
iresumeDictionary = try PropertyListSerialization.propertyList(from: data, options: PropertyListSerialization.ReadOptions(), format: nil) as? NSMutableDictionary;
} catch {}
}
return iresumeDictionary
}
func correctResumeData(_ data: Data?) -> Data? {
let kResumeCurrentRequest = "NSURLSessionResumeCurrentRequest"
let kResumeOriginalRequest = "NSURLSessionResumeOriginalRequest"
guard let data = data, let resumeDictionary = getResumeDictionary(data) else {
return nil
}
resumeDictionary[kResumeCurrentRequest] = correct(requestData: resumeDictionary[kResumeCurrentRequest] as? Data)
resumeDictionary[kResumeOriginalRequest] = correct(requestData: resumeDictionary[kResumeOriginalRequest] as? Data)
let result = try? PropertyListSerialization.data(fromPropertyList: resumeDictionary, format: PropertyListSerialization.PropertyListFormat.xml, options: PropertyListSerialization.WriteOptions())
return result
}
extension URLSession {
func correctedDownloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask {
let kResumeCurrentRequest = "NSURLSessionResumeCurrentRequest"
let kResumeOriginalRequest = "NSURLSessionResumeOriginalRequest"
let cData = correctResumeData(resumeData) ?? resumeData
let task = self.downloadTask(withResumeData: cData)
// a compensation for inability to set task requests in CFNetwork.
// While you still get -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL error,
// this section will set them to real objects
if let resumeDic = getResumeDictionary(cData) {
if task.originalRequest == nil, let originalReqData = resumeDic[kResumeOriginalRequest] as? Data, let originalRequest = NSKeyedUnarchiver.unarchiveObject(with: originalReqData) as? NSURLRequest {
task.setValue(originalRequest, forKey: "originalRequest")
}
if task.currentRequest == nil, let currentReqData = resumeDic[kResumeCurrentRequest] as? Data, let currentRequest = NSKeyedUnarchiver.unarchiveObject(with: currentReqData) as? NSURLRequest {
task.setValue(currentRequest, forKey: "currentRequest")
}
}
return task
}
}
发布于 2016-10-05 01:12:49
关于unsupported URL
错误和网络故障或其他故障上丢失的resumeData的部分问题,我已经与苹果一起记录了TSI,以及Quinn的最新回复:
首先,您所看到的行为肯定是NSURLSession中的一个bug。我们希望在将来的软件更新中解决这个问题。这项工作正在被追踪。我没有任何信息可以分享什么时候修复将交付给正常的iOS用户。 至于解决办法,我昨天详细研究了这个问题,现在我完全明白了失败的原因。海事组织有一个合理的方法来解决这一点,但我需要运行我的想法通过NSURLSession工程,然后我可以分享他们。我希望在接下来的一两天内收到他们的回音。请随时待命。
我会发布更新,但我相信这会给人们一些希望,至少这个问题正在被苹果所关注。
(为暂停/恢复行为提供大量的马萨维亚解决办法)
更新:
来自奎恩
确实如此。自从我们上次交谈以来(很抱歉,我花了这么长时间才回到这里;我最近被埋在了一些事件中),我代表其他一些开发人员进一步研究了这个问题,并发现: A.这个问题体现在两种情况下,即NSURLErrorCannotWriteToFile和NSURLErrorUnsupportedURL错误。我们可以围绕第一个而不是第二个工作。我已经向我的文档附加了一个更新,其中包含了详细信息。不幸的是,我们无法找到第二种症状的解决办法。唯一可行的方法是让iOS工程修复这个错误。我们希望在iOS 10软件更新中实现这一点,但我没有任何具体的细节可供分享(除了这个修补程序之外,它似乎错过了10.1总线)-:
因此,不幸的是,unsupported URL
问题无法解决,我们不得不等待bug得到修复。
NSURLErrorCannotWriteToFile
问题由上面的穆萨维代码处理。
另一项最新情况:
奎因证实了最近10.2测试版试图解决这些问题。
这个看过10.2了吗?是。解决这个问题的方法包括在第一个10.2测试版中。许多与我一起工作过的开发人员报告说,这个补丁已经失败了,但我仍然建议您自己在最新的测试版(目前是iOS 10.2 beta 2,14C5069c)上试用它。如果你遇到任何障碍请告诉我。
发布于 2016-11-09 06:44:25
这是穆萨维回答的目标-C代码。
它在iOS 9.3.5(设备)和iOS 10.1 (模拟器)中工作得很好。
首先用穆萨维亚的方式改正简历数据
- (NSData *)correctRequestData:(NSData *)data
{
if (!data) {
return nil;
}
if ([NSKeyedUnarchiver unarchiveObjectWithData:data]) {
return data;
}
NSMutableDictionary *archive = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil];
if (!archive) {
return nil;
}
int k = 0;
while ([[archive[@"$objects"] objectAtIndex:1] objectForKey:[NSString stringWithFormat:@"$%d", k]]) {
k += 1;
}
int i = 0;
while ([[archive[@"$objects"] objectAtIndex:1] objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%d", i]]) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = [arr objectAtIndex:1];
id obj;
if (dic) {
obj = [dic objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%d", i]];
if (obj) {
[dic setObject:obj forKey:[NSString stringWithFormat:@"$%d",i + k]];
[dic removeObjectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%d", i]];
arr[1] = dic;
archive[@"$objects"] = arr;
}
}
i += 1;
}
if ([[archive[@"$objects"] objectAtIndex:1] objectForKey:@"__nsurlrequest_proto_props"]) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = [arr objectAtIndex:1];
if (dic) {
id obj;
obj = [dic objectForKey:@"__nsurlrequest_proto_props"];
if (obj) {
[dic setObject:obj forKey:[NSString stringWithFormat:@"$%d",i + k]];
[dic removeObjectForKey:@"__nsurlrequest_proto_props"];
arr[1] = dic;
archive[@"$objects"] = arr;
}
}
}
id obj = [archive[@"$top"] objectForKey:@"NSKeyedArchiveRootObjectKey"];
if (obj) {
[archive[@"$top"] setObject:obj forKey:NSKeyedArchiveRootObjectKey];
[archive[@"$top"] removeObjectForKey:@"NSKeyedArchiveRootObjectKey"];
}
NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
return result;
}
- (NSMutableDictionary *)getResumDictionary:(NSData *)data
{
NSMutableDictionary *iresumeDictionary;
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion >= 10) {
NSMutableDictionary *root;
NSKeyedUnarchiver *keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSError *error = nil;
root = [keyedUnarchiver decodeTopLevelObjectForKey:@"NSKeyedArchiveRootObjectKey" error:&error];
if (!root) {
root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
}
[keyedUnarchiver finishDecoding];
iresumeDictionary = root;
}
if (!iresumeDictionary) {
iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:0 format:nil error:nil];
}
return iresumeDictionary;
}
static NSString * kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
static NSString * kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
- (NSData *)correctResumData:(NSData *)data
{
NSMutableDictionary *resumeDictionary = [self getResumDictionary:data];
if (!data || !resumeDictionary) {
return nil;
}
resumeDictionary[kResumeCurrentRequest] = [self correctRequestData:[resumeDictionary objectForKey:kResumeCurrentRequest]];
resumeDictionary[kResumeOriginalRequest] = [self correctRequestData:[resumeDictionary objectForKey:kResumeOriginalRequest]];
NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
return result;
}
我没有为NSURLSession创建一个类别,我只是在我的Singleton中创建。下面是创建NSURLSessionDownloadTask的代码:
NSData *cData = [self correctResumData:self.resumeData];
if (!cData) {
cData = self.resumeData;
}
self.downloadTask = [self.session downloadTaskWithResumeData:cData];
if ([self getResumDictionary:cData]) {
NSDictionary *dict = [self getResumDictionary:cData];
if (!self.downloadTask.originalRequest) {
NSData *originalData = dict[kResumeOriginalRequest];
[self.downloadTask setValue:[NSKeyedUnarchiver unarchiveObjectWithData:originalData] forKey:@"originalRequest"];
}
if (!self.downloadTask.currentRequest) {
NSData *currentData = dict[kResumeCurrentRequest];
[self.downloadTask setValue:[NSKeyedUnarchiver unarchiveObjectWithData:currentData] forKey:@"currentRequest"];
}
}
https://stackoverflow.com/questions/39346231
复制相似问题