swift - 使用 URLSession 和后台获取以及使用 firebase 的远程通知

标签 swift firebase background-fetch remote-notifications urlsession

我正在尝试在此处实现一个基本函数,当我的应用程序后台运行或挂起时将调用该函数。

实际上,我们的目标是每天发送大约 5 个,因此 Apple 不应限制我们的使用。

我已经将以下使用 firebase 和 userNotifications 的内容放在一起,目前,它在我的应用程序委托(delegate)中。

import Firebase
import FirebaseMessaging
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var backgroundSessionCompletionHandler: (() -> Void)?


    lazy var downloadsSession: Foundation.URLSession = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "bgSessionConfiguration")
        configuration.timeoutIntervalForRequest = 30.0
        let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
        return session
    }()



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FIRApp.configure()
        if #available(iOS 10.0, *) {
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })

            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            // For iOS 10 data message (sent via FCM)
            FIRMessaging.messaging().remoteMessageDelegate = self

        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
        let token = FIRInstanceID.instanceID().token()!
        print("token is \(token) < ")

        return true
    }



    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void){
           print("in handleEventsForBackgroundURLSession")
           _ = self.downloadsSession
           self.backgroundSessionCompletionHandler = completionHandler
    }

    //MARK: SyncFunc

    func startDownload() {
        NSLog("in startDownload func")

        let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
        guard let url = URL(string: todoEndpoint) else {
            print("Error: cannot create URL")
            return
        }

        // make the request
        let task = downloadsSession.downloadTask(with: url)
        task.resume()
        NSLog(" ")
        NSLog(" ")

    }

    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession){
        DispatchQueue.main.async(execute: {
            self.backgroundSessionCompletionHandler?()
            self.backgroundSessionCompletionHandler = nil
        })
    }

    func application(_ application: UIApplication,  didReceiveRemoteNotification userInfo: [NSObject : AnyObject],  fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

        NSLog("in didReceiveRemoteNotification")
        NSLog("%@", userInfo)
        startDownload()

        DispatchQueue.main.async {
            completionHandler(UIBackgroundFetchResult.newData)
        }
    }

}

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {

    // Receive displayed notifications for iOS 10 devices.
    /*
   func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        // Print message ID.
        //print("Message ID: \(userInfo["gcm.message_id"]!)")

        // Print full message.
        print("%@", userInfo)
        startDownload()  

             DispatchQueue.main.async {
                completionHandler(UNNotificationPresentationOptions.alert)
             }          
    }
    */
}

extension AppDelegate : FIRMessagingDelegate {
    // Receive data message on iOS 10 devices.
    func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
        print("%@", remoteMessage.appData)
    }
}

extension AppDelegate: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
        NSLog("finished downloading")
    }
}

结果如下:

当应用程序在前台时:

  1. 我在“startDownload func”中获取日志

  2. 我得到日志“完成下载”。

当应用在后台时:

  1. 我在“startDownload func”中获取日志

  2. 我没有收到“下载完成”的日志。

  3. 消音器不工作,即当应用程序在后台运行时,我仍在托盘中收到通知。

我正在使用 Postman 发送请求并尝试了以下负载,这导致了控制台错误 'FIRMessaging receiving notification in invalid state 2':

{
    "to" : "Server_Key", 
    "content_available" : true,
    "notification": {
    "body": "Firebase Cloud Message29- BG CA1"
  }
}

我设置了后台获取和远程通知的功能。该应用程序使用 swift 3 编写,并使用最新的 Firebase

编辑:根据评论更新了 AppDelegate 以包含函数

最佳答案

一些观察:

  1. 当您的应用程序由 handleEventsForBackgroundURLSessionIdentifier 重新启动时,您不仅需要保存完成处理程序,而且实际上还必须启动 session 。您似乎在做前者,而不是后者。

    此外,您还必须实现 urlSessionDidFinishEvents(forBackgroundURLSession:)并调用(并丢弃您对)保存的完成处理程序。

  2. 您似乎在执行数据任务。但是如果你想要后台运行,它必须是下载或上传任务。 [你已经编辑问题使其成为下载任务。]

  3. userNotificationCenter(_:willPresent:completionHandler:) 中,您永远不会调用传递给此方法的完成处理程序。因此,当 30 秒(或任何时间)到期时,由于您没有调用它,您的应用将立即终止,所有后台请求都将被取消。

    因此,willPresent 应该在完成启动请求后立即调用其完成处理程序。不要将此完成处理程序(您已完成处理通知)与稍后提供给 urlSessionDidFinishEvents 的单独完成处理程序(您已完成处理后台 URLSession 事件)。

  4. 您保存的后台 session 完成处理程序不正确。我建议:

    var backgroundSessionCompletionHandler: (() -> Void)?
    

    当你保存它时,它是:

    backgroundSessionCompletionHandler = completionHandler   // note, no ()
    

    当你在 urlSessionDidFinishEvents 中调用它时,它是:

    DispatchQueue.main.async {
        self.backgroundSessionCompletionHandler?()
        self.backgroundSessionCompletionHandler = nil
    }
    

关于swift - 使用 URLSession 和后台获取以及使用 firebase 的远程通知,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41488989/

相关文章:

swift - Alamofire 2,如何获取 NSError?

swift - 为所有 rest api 服务声明一个通用函数

ios - 附加 View Controller 中的导航栏

javascript - Firebase 和主干网

ios - Swift 中的 func 会抛出什么类型的错误?

java - 如何从Firebase Firestore实时更新文档中获取修改后的字段或数据?

android - 为个人用户组织 Firebase 数据库,想法?

iOS 后台传输 - com.apple.nsurlsessiond 文件夹充满 tmp 文件

ios - 如何在 ios swift 中正确实现后台获取

ios - 如何检测我的应用程序是否已在 didFinishLaunchingWithOptions 方法中启动以进行后台提取?