NSURLSessionDownloadTask 移动临时文件

Posted

技术标签:

【中文标题】NSURLSessionDownloadTask 移动临时文件【英文标题】:NSURLSessionDownloadTask move temporary file 【发布时间】:2018-01-02 10:51:49 【问题描述】:

我正在尝试访问我之前通过NSURLSession 下载的文件。似乎我无法读取文件的位置,即使我在委托方法结束之前执行它(因为文件是临时的)。

不过,当我尝试访问从NSURLSession delegate 返回的位置下的数据时,我得到了nil 和错误 257。

代码如下:

- (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,可能是项目的某些配置无效,或者我在应用程序的其他地方使用 backgroundTasks 的事实搞砸了

2) 如果我将此下载的源文件放到 Carthage 链接的外部框架中,则会发生与 1 相同的情况

3) 在我创建的演示项目中(在 1 和 2 中使用的复制粘贴文件)一切正常。

如果有人知道什么会导致它无法正常工作 - 那就太棒了。

【问题讨论】:

你能解决这个问题吗?我面临着类似的问题。请帮忙 @manish 我解决了,我认为它与迦太基有关。那是3年前的事了,具体原因我不记得了。你到底是什么问题? 【参考方案1】:

讨论晚了,但我看到了相同/相似的问题并进行了调查。

根据我的调查,您所看到的是预期的(我应该说“它已被观察到”吗?)。 但由于我不能 100% 确定您是如何使用 URL 会话的,所以我将我正在尝试做的事情放在下面,然后是我的观察结果(仅重要的观察结果)。 另外,我创建了一个sample project 并将其放在here 作为downLoader.zip。您可以播放并检查 URL 会话后台下载的工作方式。但是在尝试演奏之前最好先阅读下面的内容,尽管这是一个相当长的注释。

A.我正在尝试做的事情

我正在开发一种地图应用程序,我需要一次下载 1000 多个小尺寸(0.5-150kB,主要是 ~20kB)PNG 文件。它们的总下载大小约为 50MB,下载所有文件需要几分钟。我认为让用户在我的应用程序中等待下载是不好的设计,所以我让应用程序使用 URL Session 的后台下载。 然而,我不得不承认,Apple’s doc 说“后台会话经过优化,可以传输少量可以在必要时恢复的大型资源。”所以,我使用后台会话的方式完全相反。但是,无论如何...

B.我观察到的

下面,我列出了我的观察结果和我对原因的猜测。我应该说是猜测,因为它们没有记录在案。

(1) 观察:有时,didFinishDownlaodingTo 应该附带的文件不存在。

临时文件存放在: /var/mobile/Containers/Data/Application/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp。 randomHexString 从应用程序的运行更改为运行。当文件不存在时,didFinishDownloadingTo 附带的 randomHexString 是来自“上一个”会话的那个。现在,这里的“previous”是指应用程序上一次运行的会话!,在当前运行的当前会话中显然不存在。 不存在文件还有另一种情况,即randomHexString可以,但是文件不存在。这似乎发生在请求取消会话之后 (invalidateAndCancel) 和会话变得无效之前 (didBecomeInvalidWithError)。

猜想:这尤其发生在开发阶段,因为我们在下载时终止应用程序,手动或通过调试器或仅通过错误。似乎一旦下载请求被交给操作系统并被接受,即使应用程序退出,操作系统也会处理我们的请求。请注意,我们无法知道操作系统是否确实接受了请求。即使在从 URLSessionDownloadTask:resume() 返回之后,有时文件在下次启动时也不会退出,有时它们会退出。

解决方法:如果文件不存在,则忽略它们。过一会儿,“这一次”的文件应该会来。

(2) 观察:有时,重复的(相同的)文件带有 didFinishDownloadingTo。

我的应用程序将下载的 PNG 文件转换为其他格式。在 didFinishDownlaodingTo 中,我将临时文件(== OS 指定)移动到另一个我的应用程序目录,然后生成线程(GCD)以转换格式并删除下载的临时文件。因此,要覆盖的另一个线程 (didFinishDownlaodingTo) 是一个问题。

解决方法:我列出了 URLSessionTask:taskIdentifier 和 w/in didFinishDownlaodingTo,通过检索 taskID 列表来检查重复项以忽略重复文件。

(3) 观察:即使在用户终止应用后,操作系统也会重新启动应用。

在用户从任务切换器终止应用程序后,操作系统通常会使用 application:handleEventsForBackgroundURLSession:completionHandler 重新启动应用程序。 请注意,重新启动的顺序是 didFinishLaunchingWithOptions 首先作为常规启动,然后是 handleEventsForBackgroundURLSession。 从用户的 POV 来看,当他/她终止应用程序时,就是这样,完成!即使在他们终止应用程序之后,它看起来也很奇怪,它会自行重新启动并通知他们一些事情。就像僵尸一样。

猜测:Apple’s document 表示“如果用户终止您的应用程序,系统将取消所有待处理的任务”。 “待处理任务”的定义没有明确说明,但我知道这是来自 ios POV,而不是用户或程序开发人员的。正如 (1) 中所猜测的,iOS 似乎已经接受了下载请求,它们不再是“待处理的任务”了。

解决方法:在所有下载请求都通过 URLSessionDownloadTask:resume() 传递给 iOS 后,我创建了一个“flagFile”,其中的文件表示“正在下载”。当用户终止应用程序时,在 UIApplicationDelegate:applicationWillTerminate 处,我删除了标志文件。或者,如果所有下载请求都没有交给 iOS,则没有标志文件。然后在 UIApplicationDelegate:handleEventsForBackgroundURLSession 重新启动应用程序时,我检查我们是否有标志文件。如果丢失,那么我可以假设用户已终止。这里有两个选择。选择 1:我不会重新创建 URL 会话。接下来发生的事情是 iOS 将在大约 20 秒内终止我的应用程序。我不知道这(== 不创建 URL 会话)是否是合法操作,但它有效。用户可以在这 20 秒内启动,所以我添加了一些代码来处理这种情况。选择 2:我创建 URL 会话。接下来发生的是 iOS 调用委托方法 didFinishDownlaodingTo/didCompleteWithError,然后是 urlSessionDidFinishEvents。如果我在这里什么都不做,那么进程(应用程序)会无限期地保持活动状态,而不会向用户发出任何通知:任务切换器中什么都没有。这无非是浪费内存。这里的选项是触发本地通知并让用户知道我的应用程序,以便他们可以返回我的应用程序并终止(再次!),尽管我的应用程序显然显示为僵尸。两种选择都有一个问题:在某些情况下可能不会调用 applicationWillTerminate(尽管我尚未确认)。在这种情况下,标志文件保留为常规操作并向用户显示僵尸。因此,标志文件方法只是缓解问题,但我认为它在我的应用程序的大部分时间都有效。

请注意,应用程序有时会在被 xcode 调试器终止或被带有错误的操作系统 (SEGFALUT) 终止时重新启动。

(4) 观察:应用程序被终止(由用户等)然后由操作系统重新启动后,应用程序偶尔处于活动状态(UIApplication.shared.applicationState 为.active)。

我想通过本地通知通知用户下载完成,但由于它处于活动状态,本地通知不会触发。所以,我需要改用 UIAlertController 。因此,我无法提供一致的用户体验,并且对用户来说应该看起来很奇怪:大部分时间是本地通知,偶尔会有 UIAlert。请注意,当应用以活动状态启动时,它会出现在任务切换器中。

猜猜:完全不知道这是怎么发生的。一件好事(?)是这种情况只是偶尔发生。

解决方法:似乎没有。

(5) 观察:handleEventsForBackgroundURLSession/urlSessionDidFinishEvents 只被调用一次。

我在启动后台任务(application.beginBackgroundTask)后开始下载。然后在 beginBackgroundTask 的过期处理程序中,我调用 endBackgroundTask。我不知道为什么,但是在 endBackgroundTask 之后,我的进程仍然有很多处理时间,所以我可以继续请求下载。这可能是因为下载文件不断出现 w/didFinishDownlaodingTo。为了成为一个好公民,我暂停请求进一步下载,并向用户发出本地通知以将应用程序置于前台。现在,一旦我暂停请求,在 4-5 秒内,操作系统确定 URL 会话结束并调用 handleEventsForBackgroundURLSession,然后调用 urlSessionDidFinishEvents。这是一次性活动。当用户将应用程序置于前台恢复下载,然后再次将其置于后台时,不会再出现handleEventsForBackgroundURLSession/urlSessionDidFinishEvents。我不清楚的是会话开始和结束的定义。似乎会话首先从 URLSessionTask.resume() 开始,然后以超时结束,这似乎由 URLSessionConfiguration.timeoutIntervalForRequest 确定。但是,在此处设置较大的数字(1000 秒)不会影响任何事情,始终是 4-5 秒。

猜测:不知道

解决方法:当应用程序处于活动状态时,不要在 urlSessionDidFinishEvents 上进行中继。仅在操作系统重新启动应用时以及在初始 handleEventsForBackgroundURLSession/urlSessionDidFinishEvents 时进行中继。

================

下面,我列出了sample project (downLoader.zip)。您可以通过示例验证以上所有内容。

    该应用程序有一个下载文件列表。文件数为 1921 个,共 56MB。它们是 256x256 PNG 地图切片文件,位于由Geo Spatial Information Authority of Japan (GSI) 管理的服务器中。下载后,它们被移动到库/缓存/下载。如果您的设备已越狱,您可以通过 Filza 查看它们。 自行崩溃以测试重新启动 模拟后台任务到期 记录到文件,因为调试器在操作系统重新启动后无法工作。该文件在 Documents 中,可以移动到 PC 上。 玩真机。模拟器不会重新启动应用。 使用 Xcode 8.3.3 构建项目并使用 iphone6+/9.3.3 和 iphone7+/10.3.1 进行测试 要查看应用程序是否使用 xcode 重新启动,请转到“调试/附加到进程”菜单并查看是否列出了 downLoader。

================

我认为 URL 会话后台下载的行为很棘手,尤其是在重新启动时。我们至少需要考虑我上面列出的观察结果,否则应用用户会感到困惑。

【讨论】:

【参考方案2】:

我从在线用户反馈中发现了相同的错误代码 257。在错误场景中,位置总是指一个奇怪的捆绑标识符:com.sdyd.SDMobileMixSmart。而且我猜这是一个苹果系统的错误。

【讨论】:

这应该是评论,而不是答案。

以上是关于NSURLSessionDownloadTask 移动临时文件的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSessionDownloadTask 进度回调不顺畅?

在 NSMutableDictionary 中设置 NSURLSessionDownloadTask

NSUrlSessionDownloadTask - 进入后台时出现didCompleteWithError

iOS开发之网络编程--2NSURLSessionDownloadTask文件下载

NSURLSessionDownloadTask 在挂起时继续下载

在下载字节时从 NSURLSessionDownloadTask 访问字节