你问:
如何正确测试背景URLSessionDownloadTask
?
如您所指出的,您无法从Xcode调试器测试挂起/终止的应用程序会发生什么(因为调试器将保持应用程序运行).因此,只需在设备上安装应用程序,然后直接在设备上启动它,而不是从Xcode启动.
因此,这就引出了一个问题,即如何确认后台执行期间发生的事情.具体来说,问题是在没有Xcode控制台或前台运行的应用程序的情况下如何监控正在发生的事情.
我使用多种技术来监控后台执行期间发生的事情:
当在后台完成下载时(至少在测试时),我会让应用程序呈现"用户通知".通过这种方式,即使应用程序在后台运行,我也会获得视觉确认.
因此,当应用程序处于前台时,我请求用户通知权限:
import UserNotifications
和
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UNUserNotificationCenter.current().requestAuthorization(options: .alert) { granted, error in
print(granted, String(describing: error))
}
}
和 then, when the download is done, I post a user notification:
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
UNUserNotificationCenter.current().add(title: "Downloads finished", body: "Hurray")
// then continue doing other stuff; e.g., in this method, I
// would often call the saved completion handler, if any
//
// DispatchQueue.main.async {
// self.savedCompletionHandler?()
// self.savedCompletionHandler = nil
// }
}
}
extension UNUserNotificationCenter {
func add(title: String, body: String) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: Bundle.main.bundleIdentifier!, content: content, trigger: trigger)
add(request)
}
}
另一种方法是观看来自macOS Console
的iOS日志(log)消息.为此,请创建Logger
:
import os.log
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "BackgroundSession")
和 then I will have key events log messages.
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
logger.debug(#function)
…
}
}
然后我可以在我的macOS Console
应用程序上观看这些iOS活动.请参阅Swift: print() vs println() vs NSLog()中的Logger
讨论.
如果这些下载需要很长时间,以至于盯着macOS Console
不切实际,我有时会将这些事件记录到某个持久性存储中(例如,无论是SwiftData/CoreData还是只是将它们写入文本或杨森文件).然后我可以稍后从Xcode"设备"窗口下载该内容.(对于背景URLSession
,上述Console
方法就足够了,但该技术对于计时不太及时/可预测的背景事件(例如BGProcessingTask
、BGAppRefreshTask
等)非常有用.)
您提到了设备上的"快速应用程序终止"开发人员设置.测试这种"后台重新启动"功能没有必要.是的,测试"如果应用程序在其正常生命周期中被终止怎么办"工作流(以确保后台URLSession
被正确重新实例化)的场景是有用的.但没有必要简单地验证"后台重新启动应用程序"功能.
您说尽管没有实现handleEventsForBackgroundURLSession
和urlSessionDidFinishEvents
,但它似乎有效.
如果您不希望您的应用程序在后台请求完成时被唤醒,则无需实现这些功能(即,您明确将sessionSendsLaunchEvents
设置为false
).
但如果您确实希望在网络请求完成时在后台启动您的应用程序,Apple非常明确地表示我们应该实现这两种方法.这种模式背后的 idea 是,如果应用程序在后台启动,我们希望在完成时告诉操作系统,并且应用程序可以再次暂停.文档并不总是明确说明未能实现所有必要的委托方法的影响,但通常,未能履行后台执行义务的应用程序future 可能没有资格进行后台执行.苹果对滥用后台执行的应用程序越来越严格,因此我建议按照documentation中的建议实施API.
您分享了文档中的一句话,其中写道:
恢复的应用程序调用完成处理程序后,下载任务就会完成其工作并调用委托的urlSession(_:downloadTask:didFinishDownloadingTo:)
方法.
这似乎意味着didFinishDownloadingTo
被称为after,你称之为关闭.事实并非如此.如下所述,urlSessionDidFinishEvents
(这是我们经常调用保存的Close的地方)只有在所有任务委托方法(包括didFinishDownloadingTo
)被调用后才会调用,而不是在之前.
此外,正确的事件顺序已在同一document个中概述:
当所有事件都已交付时,系统将调用URLSessionDelegate
的urlSessionDidFinishEvents(forBackgroundURLSession:)
方法.此时,获取应用程序委托存储的[已保存的完成处理程序].
我刚刚做了一个实证测试来验证事件的顺序.这证实了如果您的应用程序在上传/下载完成时被唤醒,则事件顺序如下:
当应用程序运行时,创建背景URLSession
并开始下载.我只是在他们暂停应用程序时离开应用程序.一些观察:
如果下载完成时应用程序未运行,则会调用应用程序委托的handleEventsForBackgroundURLSession
,因此应用程序将在后台重新启动以让您处理任务.如果调用此方法时,我们
保存关闭以供将来参考,并将在下面的步骤4中使用它.
如果应用程序之前已被终止,那么我们显然会使用相同的标识符重新创建后台URLSession
会话.如果应用程序只是被暂停(这更有可能),那么显然我们仍然在运行现有的后台URLSession
.
显然,只有在应用程序被唤醒并在后台启动时,才会调用此操作.
不用说,如果下载完成时应用程序恰好在前台运行,则不会调用此事件.但您仍然必须实现此方法才能支持应用程序已暂停/终止的情况.(如果您不担心应用程序在下载过程中被暂停/终止,那么您可能根本不会使用后台URLSession
.)
调用所有任务委托方法(例如,didFinishDownloadingTo
、didCompleteWithError
等)用于已完成的后台URLSession
任务.
只有当所有这些活动结束后,urlSessionDidFinishEvents
人才会被召集.这通常是我们在步骤2中收到的保存的完成处理程序关闭的地方.(有些人可能会注意到,如果您在步骤3中启动了额外的同步事件,那么您会推迟对关闭的调用,直到所有这些都完成,但这有点边缘情况.)
请注意:我已经从您的原始列表中交换了3 4,因为任务委托方法称为before urlSessionDidFinishEvents
is.