iOS 操作扩展中的后台任务

Background task in iOS action extension

我正在为一个应用程序开发一个操作扩展,它为用户准备一些数据,然后使用 MailCore2 将此信息发送到 SMTP 服务器。准备数据的速度非常快,但发送电子邮件可能需要一些时间(取决于其大小)。这就是为什么我正在寻找一种方法来处理后台发送活动。但这会导致一些不同解决方案的问题:

  1. 使用 URLSession。这是在 iOS 扩展中处理大量上传或下载的第一种方法。但问题是 MailCore2 并没有使用 URLSessions 将数据传输到邮件服务器。所以这是不可用的。或者是否有可能以某种方式 'wrap' 在 URLSession 中进行此调用?

  2. 使用UNUserNotificationCenter并在数据准备好后从分机发送本地通知。想法是在收到这个通知后,在主应用中开始发送任务。问题:通知 userNotificationCenter didReceive 方法仅在用户单击通知时调用。一个简单的徽章通知不调用委托方法。

  3. 使用后台获取。这在模拟器中运行良好,但在 iOS 设备上连接到 SMTP 服务器时出现错误。我遇到了与 github.com/MailCore/mailcore2/issues/252 中描述的相同的 HELO 问题。对于这个问题,解决方案应该是 MCOSMTPSession.isUseHeloIPEnabled = true,但这可能不适用于所有服务器。所以也不是一个完美的解决方案。

任何想法,如何处理这样的任务?或者如何处理上述解决方案之一?

编辑: 因为这个问题还没有收到任何答案:有什么不清楚或需要哪些额外信息?

从您的回答来看,您的困难在于您需要允许后台活动。您可以通过 following a tutorial such as this one.

看来你不能从一个extension.Refer here(Some APIs Are Unavailable to App Extensions)

在您提出的解决方案中,我的建议是尝试使用 silent push notifications ,您可以在准备数据阶段调用 api ,然后从服务器发送静默推送并在静默推送到达时执行后台任务。

最有用的解决方案是使用推送通知。 我在自己的应用中使用的解决方案:

  1. 创建一个简单的服务器,它可以检索一个简单的请求:设备令牌和您想要唤醒设备的时间。
  2. 当应用程序进入后台模式时,向服务器发送请求以稍后唤醒设备。例如。 5 minutes/20 分钟等 func applicationWillEnterForeground(_ application: UIApplication) { 里面 AppDelegate.
  3. 不要忘记让应用程序尽可能在后台运行。

例如,

   @UIApplicationMain
    class AppDelegate: UIResponder {
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    func application(_ application: UIApplication,
                         didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                         fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            if UIApplication.shared.applicationState != .active {
                doFetch(completionHandler: completionHandler)
            } else {
                // foreground here
                completionHandler(UIBackgroundFetchResult.noData)
            }
        }
    func application(_ application: UIApplication,
                         performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            doFetch(completionHandler: completionHandler)
        }
    private func doFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    sendTheRequestToWakeApp()
    backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
                self?.endBackgroundTask()
            }
/// do work here
    completionHandler(UIBackgroundFetchResult.noData)
    }
    private func endBackgroundTask() {
            print("Background task ended.")
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = UIBackgroundTaskInvalid
        }
    private func sendTheRequestToWakeApp() {
    /// Implement request using native library or Alamofire. etc.
    }
    }

在服务器端使用简单的时间或循环。

缺点,

  1. 需要互联网
  2. 它并不完美。当电池电量不足时,后台模式会受到限制。

不要忘记设置项目:

经过多次测试和失败后,我找到了以下解决方案来在扩展程序的后台执行长时间执行的任务。这按预期工作,即使扩展已经完成:

func performTask()
{
    // Perform the task in background.
    let processinfo = ProcessInfo()
    processinfo.performExpiringActivity(withReason: "Long task") { (expired) in
        if (!expired) {
            // Run task synchronously.
            self.performLongTask()
        }
        else {
            // Cancel task.
            self.cancelLongTask()
        }
    }
}

此代码使用 ProcessInfo.performExpiringActivity() 在另一个线程中执行任务。 performLongTask() 中的任务同步执行很重要。当到达块的末尾时,线程将终止并结束您的任务的执行。

类似的方法也适用于主应用程序。 background tasks in iOS.

的小摘要中对其进行了详细描述