我正试图通过NSURLSession
到达我之前下载的文件。看来我无法读取文件的位置,即使我在委托方法结束之前这样做(因为文件是临时的)。
但是,当我尝试访问nil
返回的位置和错误257下的数据时,我收到NSURLSession delegate
。
代码如下:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSError *movingError = nil;
NSData *fileData = [NSData dataWithContentsOfFile:location.path options:0 error:&movingError]; // is nil
NSLog(@"%@", movingError); // is error 257
}
这段代码有什么问题..?我看到了类似的问题NSURLSessionDownloadTask - downloads but ends with error和iPhone - copying a file from resources to Documents gives an error,但这些问题完全不适用于我的情况。
- 编辑 -
我创建了一个新项目并粘贴了相同的代码。它有效...所以:
1)在我的项目中我收到错误257,可能是项目的某些配置无效或者我正在使用appTasks在应用程序的其他位置搞乱事实
2)如果我将此下载的源文件放入由Carthage链接的外部框架,则与1相同
3)在我创建的演示项目中(1和2中使用的复制粘贴文件)一切正常。
如果有人知道什么可能导致它不起作用的事实 - 会很棒。
答案 0 :(得分:2)
讨论的后期,但我已经看到相同/类似的问题并进行了调查。
通过我的调查,你所看到的是预期的(我应该说“它被观察到了吗?”)。 但由于我并不是100%确定你如何使用URL会话,所以我将其放在我想要做的之下,然后是我的观察(仅限重要的)。 另外,我创建了一个sample project并将其here作为 downLoader.zip 。您可以播放并检查URL会话后台下载的工作方式。但是在尝试玩之前通过下面阅读会更好,尽管这是一个相当长的注意事项。
一个。我想做什么
我正在开发一种地图应用,我需要一次下载1000多个小尺寸(0.5-150kB,大多数~20kB)的PNG文件。它们的总下载大小约为50MB,下载所有这些内容需要几分钟。我认为将用户留在我的应用程序只是等待下载是糟糕的设计,所以我让应用程序使用URL Session的后台下载。 但是,我必须承认,Apple’s doc表示“后台会话已经过优化,可以根据需要传输少量可以恢复的大型资源。”因此,我使用后台会话的方式完全相反。但是,无论如何......
B中。我所观察到的是什么
下面,我列出了我的观察结果以及我对原因的猜测。我应该说猜测,因为他们没有记录。
(1)观察:有时,应该带有didFinishDownlaoding的文件不存在。
临时文件放在: /var/mobile/Containers/Data/Application/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp。 randomHexString从应用程序的运行更改为运行。当文件不存在时,didFinishDownloadingTo附带的randomHexString是“previous”会话中的randomHexString。现在,“previous”在这里意味着应用程序之前运行的会话!在当前运行的当前会话中显然不存在。 对于不存在的文件还有另一种情况,即randomHexString没问题,但文件不存在。这似乎发生在请求会话取消(invalidateAndCancel)之后和会话变为无效之前(didBecomeInvalidWithError)。
猜猜:这种情况尤其发生在开发阶段,因为我们在下载时,手动或通过调试器或仅通过bug来终止应用程序。一旦下载请求被移交并被操作系统接受,操作系统即使在应用程序退出后也会处理我们的请求。请注意,我们无法知道操作系统是否已明确接受了请求。即使从URLSessionDownloadTask:resume()返回后,有时文件也不会在下次启动时退出,有时它们也会退出。
解决方法:如果文件不存在,请忽略它们。有一段时间,“这个时间”的文件应该来了。
(2)观察:有时,重复(相同)文件附带didFinishDownloadingTo。
我的应用将下载的PNG文件转换为其他格式。 W / in didFinishDownlaodingTo,我将临时文件(== OS指定)移动到我的应用程序的另一个目录,然后生成线程(GCD)以转换格式并删除下载的临时文件。因此,要覆盖的另一个线程(didFinishDownlaodingTo)是一个问题。
解决方法:我列出了URLSessionTask:taskIdentifier和w / in didFinishDownlaodingTo,通过检索taskID列表来检查重复,以忽略重复的文件。
(3)观察:即使用户终止了应用程序,操作系统也会重新启动应用程序。
用户从任务切换器终止应用程序后,操作系统会重新启动应用程序w / application:handleEventsForBackgroundURLSession:completionHandler。 请注意,重新启动的顺序是didFinishLaunchingWithOptions首先作为常规启动,然后是handleEventsForBackgroundURLSession。 从用户的POV,当他/她终止应用程序时,就是这样,完成了!它甚至在终止应用程序后看起来很奇怪,它会重新启动并向他们通知某些内容。它就像一个僵尸。
猜猜:Apple’s document说“如果用户终止您的应用,系统会取消所有待处理的任务”。 “待定任务”的定义没有明确说明,但据我所知,这是来自iOS POV而非用户或程序开发人员。正如(1)中所猜测的那样,iOS似乎已经接受了下载请求,而且它们不再是“待定任务”。
解决方法:在将所有下载请求传递给iOS后,我创建了一个“flagFile”w / URLSessionDownloadTask:resume(),其中的文件意味着“正在下载”。当用户终止应用程序时,在UIApplicationDelegate:applicationWillTerminate,我删除了标志文件。或者,如果所有下载请求都没有传递给iOS,那么就没有标记文件。然后在应用程序重新启动UIApplicationDelegate:handleEventsForBackgroundURLSession,我检查是否有标志文件。如果遗漏,那么我可以假设用户终止了。这里有两个选择。选择1:我不会重新创建URL会话。接下来会发生什么,iOS将在大约20秒后终止我的应用程序。我不知道这是否(==没有创建URL会话)是合法的操作,但它的工作原理。用户可以在这20秒内启动,所以我添加了一些代码来处理这种情况。选择2:我创建了URL会话。接下来发生的事情是iOS调用委托方法didFinishDownlaodingTo / didCompleteWithError,然后是urlSessionDidFinishEvents。如果我不在这里做任何事情,那么进程(app)将无限期地保持活动而不向用户发送任何通知:任务切换器中没有任何内容。这只不过是浪费记忆。这里的选项是触发本地通知,让用户知道我的应用程序,以便他们可以返回我的应用程序并终止(再次!),虽然我的应用程序显然是一个僵尸。两种选择都有一个问题:在某些情况下可能不会调用applicationWillTerminate(尽管我已经确认)。在这种情况下,标记文件将保留为常规操作并向用户显示僵尸。因此,标记文件方法只是缓解了这个问题,但我认为它大部分时间都适用于我的应用程序。
请注意,当应用程序被xcode调试程序杀死或被OS w / bug(SEGFALUT)杀死时,应用程序会重新启动。
(4)观察:应用程序终止后(由用户等)然后由OS重新启动,应用程序偶尔处于活动状态(UIApplication.shared.applicationState为.active)。
我想通过本地通知通知用户下载完成,但由于它处于活动状态,因此本地通知不会触发。所以,我需要使用UIAlertController。因此,我无法提供一致的用户体验,对用户来说应该看起来很奇怪:大部分时间都是本地通知,偶尔也会出现UIAlert。请注意,当应用程序以活动状态启动时,它将显示在任务切换器中。
猜猜:完全不知道这是怎么发生的。一个好的(?)事情只是偶尔发生。解决方法:似乎没有。
(5)观察:handleEventsForBackgroundURLSession / urlSessionDidFinishEvents只被调用一次。
我在启动后台任务(application.beginBackgroundTask)后开始下载。然后在beginBackgroundTask的expiration处理程序中,我调用endBackgroundTask。我不确切知道为什么,但在endBackgroundTask之后,我的进程仍然有很多处理时间,所以我可以继续请求下载。这可能是因为下载文件继续w / didFinishDownlaodingTo。为了成为一个好公民,我暂停请求进一步下载,并向用户发送本地通知以将应用程序置于前台。现在,一旦我暂停请求,在4-5秒内,OS确定URL会话结束并且handleEventsForBackgroundURLSession然后调用urlSessionDidFinishEvents。这是一次性活动。当用户将应用程序置于前台以恢复下载,然后再将其放入后台时,将不再出现handleEventsForBackgroundURLSession / urlSessionDidFinishEvents。对我来说不清楚的是会话的开始和结束的定义。似乎会话在第一个URLSessionTask.resume()开始,然后结束w / timeout,这似乎由URLSessionConfiguration.timeoutIntervalForRequest确定。但是,在这里设置大号(1000秒)并不会影响任何事情,它总是4-5秒。
猜猜:不知道
解决方法:在应用程序处于活动状态时,不要在urlSessionDidFinishEvents上进行中继。仅在操作系统重新启动应用程序时以及初始handleEventsForBackgroundURLSession / urlSessionDidFinishEvents时重新启动。
===============
下面,我列出了sample project(downLoader.zip)。您可以使用样本验证以上所有内容。
===============
我认为URL会话后台下载行为很棘手,特别是在重新启动时。我们至少需要考虑上面列出的观察结果,否则应用用户会感到困惑。