ios - NSURLSessionDownloadTask移动临时文件

标签 ios objective-c permissions nsurlsession nsfilemanager

我正在尝试访问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 erroriPhone - copying a file from resources to Documents gives an error,但这完全不适用于我的情况。

-编辑-

我创建了一个新项目,并粘贴了相同的代码。它的工作原理如下:

1)在我的项目中,我收到错误257,可能是项目的某些配置无效,或者我在应用程序中的其他地方使用了backgroundTasks的事实

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

3)在我创建的演示项目中(在1和2中使用了复制粘贴的文件),所有内容都可以正确运行。

如果某人有一个想法可以导致它不起作用的事实,那就太棒了。

最佳答案

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

通过我的调查,您所看到的是预期的(我应该说“已观察到”吗?)。
但是,由于我不确定100%是否使用URL会话,因此我将要尝试的内容放在下面,然后是我的观察(仅重要的观察)。
另外,我创建了一个sample project并将其放在downLoader.zip中。您可以播放并检查URL会话后台下载的工作方式。但是最好先通读以下内容,然后再尝试演奏,尽管这是一个很长的音符。

答:我要做什么

我正在开发一种地图应用程序,我需要一次下载1000多个小尺寸(0.5-150kB,主要是〜20kB)PNG文件。它们的总下载大小约为50MB,需要花费几分钟的时间来下载所有它们。我认为让用户等待我的应用程序等待下载是错误的设计,因此我使该应用程序使用URL Session的后台下载。
但是,我不得不承认here表示“优化了后台会话,以便传输少量可以在必要时恢复的大型资源。”因此,我使用后台会话的方式是完全相反的。但不管怎么说...

我观察到的

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

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

临时文件位于:
/ var / mobile /容器/数据/应用程序/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp。应用运行时,randomHexString会发生变化。如果文件不存在,则didFinishDownloadingTo随附的randomHexString是“上一个”会话中的一个。现在,“上一个”是指应用程序上次运行的会话!,当前运行时的当前会话显然不存在该会话。
对于不存在的文件,还有另一种情况,即randomHexString可以,但是文件不存在。这似乎发生在请求取消会话之后(invalidateAndCancel),而在会话变为无效之前(didBecomeInvalidWithError)。

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

解决方法:如果文件不存在,请忽略它们。一段时间后,应该会出现“这次”的文件。

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

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

解决方法:我列出URLSessionTask:taskIdentifier并在didFinishDownlaodingTo中,通过检索taskID列表来检查重复项,以忽略重复的文件。

(3)观察:即使用户终止了该应用程序,操作系统仍会重新启动该应用程序。

用户从任务切换器终止应用程序后,操作系统通常会通过应用程序:handleEventsForBackgroundURLSession:completionHandler重新启动应用程序。
请注意,重新启动的顺序是didFinishLaunchingWithOptions首先作为常规启动出现,然后是handleEventsForBackgroundURLSession。
从用户的角度来看,当他/她终止应用程序时,就完成了!即使他们终止了该应用程序,它看起来仍然很奇怪,它本身会重新启动并通知他们一些东西。就像僵尸一样。

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

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

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

(4)观察:在应用终止(由用户等)然后由OS重新启动之后,该应用有时处于 Activity 状态(UIApplication.shared.applicationState为.active)。

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

猜猜:完全不知道如何发生。一件好事?这只是偶尔发生。

解决方法:似乎没有。

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

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

猜猜:不知道

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

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

在下面,我列出了URLSessionConfiguration.timeoutIntervalForRequest(downLoader.zip)。您可以使用示例验证以上所有内容。

  • 该应用程序具有下载文件列表。文件数为1921,总共56MB。它们是位于sample project(GSI)管理的服务器中的256x256 PNG地图图块文件。下载后,它们被移至Library / Cache / download。如果您的设备越狱,则可以使用Filza查看它们。
  • 崩溃以测试
  • 重新启动
  • 模拟后台任务到期
  • 记录到文件,因为操作系统重新启动后调试器无法工作。该文件在文档中,可以移动到PC。
  • 玩真实的设备。模拟器不会重新启动该应用程序。
  • 使用Xcode 8.3.3构建的项目并通过iphone6 + / 9.3.3和iphone7 + / 10.3.1进行了测试
  • 要查看是否使用xcode重新启动了应用程序,请转到“调试/附加到进程”菜单,然后查看是否列出了downLoader。

  • ===============

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

    关于ios - NSURLSessionDownloadTask移动临时文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45338412/

    相关文章:

    linux - 如何为不同的用户维护不同的编译库?

    ios - iPad 中的 UIWebView 后退按钮实现问题

    ios - sharedApplication() 在 Today Extension 中无法快速工作

    ios - UITableView 显示,但单元格中没有任何名称

    iphone - didSelectRowAtIndexPath 与部分

    sharepoint - Biztalk Sharepoint 适配器权限错误

    ios - 加载 .ics 文件以从应用程序添加日历事件

    ios - 插入耳机时如何使用背景音轨录制语音

    ios - MPMoviePlayer 在查找文件时播放音频但不播放视频

    php - Laravel 5.5 权限 - 用户没有正确的角色