NSURLSessionDownloadTask移动临时文件 [英] NSURLSessionDownloadTask move temporary file

查看:132
本文介绍了NSURLSessionDownloadTask移动临时文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试访问NSURLSession之前下载的文件.似乎我无法读取文件的位置,即使我在委托方法结束之前就已经做了(因为文件是临时的).

I'm trying to reach the file which I was downloading earlier by NSURLSession. It seems I can't read the location of the file, even though I'm doing it before delegate method ends (as the file is temporary).

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

Still, I'm getting nil when trying to access the data under location returned from NSURLSession delegate and error 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-下载但以错误结尾iPhone-将文件从资源复制到文档会出现错误,但这些完全不适用于我的情况.

What's wrong with this code..? I saw similar questions NSURLSessionDownloadTask - downloads but ends with error and iPhone - copying a file from resources to Documents gives an error but these completely doesn't apply to my case.

-编辑-

我创建了一个新项目,并粘贴了相同的代码.它行得通...所以:

I've created a new project and pasted the very same code. It works... So:

1)在我的项目中,我收到错误257,该项目的某些配置可能无效,或者我在应用程序中的其他位置使用backgroundTasks的事实使事情变得混乱

1) In my project I'm receiving error 257, probably some configuration of the project is invalid or the fact I'm using backgroundTasks somewhere else in the app messes things up

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

2) Same as in 1 happens if I put the source files of this download to the external framework linked in by Carthage

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

3) On demo project I created (copy-pasted files used in 1 & 2) everything works corretly.

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

If someone has an idea what can cause the fact it isn't working - would be awesome.

推荐答案

该讨论已结束,但我看到了相同/相似的问题并进行了调查.

Late to the discussion, but I've seen the same/similar issue and investigated.

根据我的调查,您所看到的是预期的(我应该说已观察到"吗?). 但是由于我不确定100%如何使用URL会话,因此我将要尝试的内容放在下面,然后是我的观察(仅重要的观察). 另外,我创建了一个示例项目,并将其放入

With my investigation, what you are seeing is expected (should I say "it's observed"?). But since I'm not 100% sure how you are using URL session, I put down below what I'm trying to do, followed by my observations (important ones only). Also, I created a sample project and put it here as downLoader.zip. You can play and check how URL session background download works. But It's better to read thru below before trying to play, though this is fairly a long note.

A.我想做什么

我正在开发一种地图应用程序,我需要一次下载1000多个小尺寸(0.5-150kB,大部分为〜20kB)PNG文件.它们的总下载大小约为50MB,需要花费几分钟的时间来下载所有这些文件.我认为让用户等待我的应用程序等待下载是错误的设计,因此我使该应用程序使用URL Session的后台下载. 但是,我必须承认, Apple的文档说,后台会话已针对转移少量可以根据需要恢复的大型资源."因此,我使用后台会话的方式是完全相反的.但是,无论如何...

I'm developing kind a map app, where I need to download 1000+ of small size (0.5-150kB, mostly ~20kB) PNG files at a time. Total download size of them is ~50MB, and it takes a couple of minutes to download all of them. I think that keeping users to my app just waiting for download is bad design, so I made the app to use URL Session’s background download. I have to admit, however, that Apple’s doc says that "Background sessions are optimized for transferring a small number of large resources that can be resumed as necessary." So, the way I’m using background session is totally opposite. But, anyway...

B.我观察到的

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

Below, I’ve listed my observations and my guess of why. I should say guess since they are not documented.

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

(1) observation: sometimes, files that should come with didFinishDownlaodingTo don’t exist.

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

Temporary files are put at: /var/mobile/Containers/Data/Application/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp. The randomHexString changes from app’s run to run. When the files don’t exist, the randomHexString that came with didFinishDownloadingTo is the one from the "previous" session. Now, "previous" here means the session at app’s previous run !, which clearly doesn't exist at the current session at current run. There's another scenario for non-existent file, which is that the randomHexString is ok, but the files don't exist. This seems to happen after session cancel is requested (invalidateAndCancel) and before session becomes invalid (didBecomeInvalidWithError).

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

Guess: This happens especially on the development phase since we terminate the app while downloading, manually or by debugger or just by bug. It seems once the download requests are handed and accepted to the OS, the OS handles our requests even after the app quits. Note we can’t know if the OS has definitely accepted the requests or not. Even after the return from URLSessionDownloadTask:resume(), sometimes files don’t exit at the next launch and sometimes they do.

解决方法:如果文件不存在,请忽略它们.过一会儿,这次"的文件就会出现.

Workaround: If files don’t exist, just ignore them. In a while, "this time’s" files should come.

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

(2) observation: sometimes, the duplicated (same) files come with didFinishDownloadingTo.

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

My app converts downloaded PNG files to other format. W/in didFinishDownlaodingTo, I move temp files (== OS designated) to another my app’s directory, then spawn thread (GCD) to convert the format and delete downloaded temp files. So, the other thread (didFinishDownlaodingTo) to overwrite is an issue.

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

Workaround: I make list of URLSessionTask:taskIdentifier and w/in didFinishDownlaodingTo, check the duplication by retrieving the taskID list to ignore duplicated files.

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

(3) observation: even after the user terminated the app, OS relaunches the app again.

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

After the user terminated the app from task switcher, quite often, the OS relaunches the app w/ application:handleEventsForBackgroundURLSession:completionHandler. Note the sequence of relaunch is that didFinishLaunchingWithOptions comes first as regular launch, then handleEventsForBackgroundURLSession comes next. From user’s POV, when he/she terminated the app, that’s it, done! It looks strange even after they terminated the app, it relaunches in itself and notifies something to them. It's like a zombie.

猜想:

Guess: The Apple’s document says "If the user terminates your app, the system cancels any pending tasks". The definition of "pending tasks" is not clearly stated but I understand this is from iOS POV and not user’s or program developer’s. As guessed in (1), iOS seemed to have accepted the download requests, and they are not "pending tasks" anymore.

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

Workaround: I create a "flagFile" after all of download requests are handed to iOS w/ URLSessionDownloadTask:resume(), of which file means that "download is ongoing". When users terminate the app, at UIApplicationDelegate:applicationWillTerminate, I delete the flag file. Or, if all of download requests not handed to iOS, there's no flag file. Then at app’s relaunch at UIApplicationDelegate:handleEventsForBackgroundURLSession, I check if we have the flag file. If missing, then I can assume that users terminated. Two choices here. Choice-1: I will not recreate URL session. What happens next is that iOS will terminate my app in about 20 seconds. I have no idea if this (== not creating the URL session) is a legal operation but it works. Users can launch w/in this 20 sec, so I put some more code to handle that scenario. Choice-2: I create URL session. What happens next is that iOS calls delegate methods didFinishDownlaodingTo/didCompleteWithError, followed by urlSessionDidFinishEvents. If I don't do anything here, the process (app) keeps alive indefinitely w/o any notification to users: nothing in task switcher. This is nothing more than waste of memory. The option here is to fire local notification and let users know of my app, so that they can go back to my app and can terminate (Again!), though my app clearly appears as a zombie. One issue for both choices: applicationWillTerminate may not be called in certain situation (though I've yet confirmed). At this case, flag file is left as regular ops and show zombie to users. So, the flag file method is just mitigation to the issue, but I think it works most of the time for my app.

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

Note the app is relaunched sometimes when it's killed by xcode debugger or killed by OS w/ bug (SEGFALUT).

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

(4) observation: after the app is terminated (by user, etc.) then relaunched by OS, the app is occasionally in active state (UIApplication.shared.applicationState is .active).

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

I want to notify the user on the download completion by local notification, but since it's active, local notification doesn't fire. So, I need to use UIAlertController instead. Therefore, I can't provide consistent user experience, and should look strange for users: most of the time local notification and very occasionally UIAlert. Note when app started in active state, it appears in task switcher.

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

Guess: totally no idea how this can happen. One good(?) thing is this happens only occasionally.

解决方法:似乎没有.

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

(5) observation: handleEventsForBackgroundURLSession/urlSessionDidFinishEvents is called just once.

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

I start the download after started background task (application.beginBackgroundTask). Then in expiration handler of beginBackgroundTask, I call endBackgroundTask. I don't know exactly why, but after endBackgroundTask, my process is still given lots of process time, so I can keep requesting download. This might be because download files keep coming w/ didFinishDownlaodingTo. To be a good citizen, I suspend to request further download, and fire local notification to user to put the app to foreground. Now, once I suspend the request, in 4-5 seconds, OS determines the URL session is over and handleEventsForBackgroundURLSession then urlSessionDidFinishEvents are called. This is one-off event. When the user put the app in foreground to resume download, then put it again in background, no handleEventsForBackgroundURLSession/urlSessionDidFinishEvents will come anymore. What's unclear to me is the definition of session's start and end. It seems the session starts at first URLSessionTask.resume(), then ends w/ timeout, which seems to be determined by URLSessionConfiguration.timeoutIntervalForRequest. However, setting large number here (1000 sec) doesn't affect anything, it's always 4-5 seconds.

猜猜:不知道

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

Workaround: don't relay on urlSessionDidFinishEvents while the app is alive. Relay only when app relaunched by OS and at initial handleEventsForBackgroundURLSession/urlSessionDidFinishEvents.

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

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

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

Below, I listed about the sample project (downLoader.zip). You can verify all of above with the sample.

  1. 该应用程序具有下载文件列表.文件数为1921,总共56MB.它们是256x256 PNG地图图块文件,位于由 Geo空间信息管理局管理的服务器中日本(GSI).下载后,它们将移至库/缓存/下载".如果您的设备已越狱,则可以使用Filza查看它们.
  2. 崩溃以测试重新启动
  3. 模拟后台任务到期
  4. 登录到文件,因为在操作系统重新启动后调试器无法正常工作.该文件在文档"中,可以移动到PC.
  5. 玩真实的设备.模拟器不会重新启动该应用程序.
  6. 使用Xcode 8.3.3构建的项目,并通过iphone6 +/9.3.3和iphone7 +/10.3.1进行了测试
  7. 要查看是否使用xcode重新启动应用,请转到调试/附加到进程"菜单,然后查看是否列出了downLoader.
  1. The app has a list of download files. The number of files is 1921, and 56MB in total. They are 256x256 PNG map tile files that are located in a server managed by Geo Spatial Information Authority of Japan (GSI). After downloaded, they are moved to Library/Cache/download. If your device is jail-broken, you can view them w/ Filza.
  2. crash itself to test relaunch
  3. emulate background task expiration
  4. logging to file since debugger doesn't work after relaunch by OS. The file is in Documents, can be moved to PC.
  5. Do play with the real device. Simulator doesn't relaunch the app.
  6. Project built w/ Xcode 8.3.3 and tested w/ iphone6+/9.3.3 and iphone7+/10.3.1
  7. To see if app relaunched w/ xcode, go to Debug/Attach to Process menu and see if downLoader is listed.

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

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

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

I think URL session background download behaves trickily, especially at relaunch. We need to consider at least the observations I listed above, or the app users will get confused.

这篇关于NSURLSessionDownloadTask移动临时文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆