gpt4 book ai didi

ios - 在 iOS10 上恢复 NSUrlSession

转载 作者:IT王子 更新时间:2023-10-29 07:43:25 27 4
gpt4 key购买 nike

iOS 10 即将发布,因此值得测试应用程序与它的兼容性。在这样的测试中,我们发现我们的应用无法在 iOS10 上恢复后台下载。在旧版本上运行良好的代码不适用于新版本,无论是在模拟器上还是在设备上。

我没有将我们的代码减少到最小的工作测试用例,而是在互联网上搜索 NSUrlSession 教程并对其进行了测试。行为是相同的:继续在 iOS 的旧版本上工作,但在 10th 上中断。

重现步骤:

  1. 下载一个项目表单 NSUrlSession 教程 https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
  2. 直接链接: http://www.raywenderlich.com/wp-content/uploads/2016/01/HalfTunes-Final.zip
  3. 构建它并在 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 的恢复是否在所有其他应用程序中中断?应用端可以解决吗?

最佳答案

这个问题是由于 currentRequest 和 originalRequest NSKeyArchived 用一个不寻常的“NSKeyedArchiveRootObjectKey”根编码而不是 NSKeyedArchiveRootObjectKey 常量(字面意思是“根”)和 NSURL(Mutable)Request 编码过程中的一些其他行为不当引起的。

我在 beta 1 中检测到并提交了一个错误(编号 27144153,以防你想要复制)。我什至给 NSURLSession 团队的支持人员“Quinn the Eskimo”(apple dot com 的 eskimo1)发送了一封电子邮件,以确认他们收到了邮件,他说他们收到了并意识到了问题。

更新:我终于想出了解决这个问题的方法。将数据提供给 correctResumeData() 函数,它将返回可用的简历数据

更新 2:您可以使用 NSURLSession.correctedDownloadTaskWithResumeData()/URLSession.correctedDownloadTask(withResumeData:) 函数获取具有正确 originalRequest 和 currentRequest 变量的任务

更新 3: Quinn 说此问题已在 iOS 10.2 中解决,您可以继续使用此代码以与 iOS 10.0 和 10.1 兼容,并且它可以毫无问题地与新版本一起使用。

(对于 Swift 3 代码,请在下方滚动,对于 Objective 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
}
}

关于ios - 在 iOS10 上恢复 NSUrlSession,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39346231/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com