Using URLSession and background fetch together with remote notifications using firebase

天大地大妈咪最大 提交于 2020-01-21 19:15:48

问题


I am trying to implement a basic func here which will be called when my app is backgrounded or suspended.

In reality, we aim to send about 5 a day so Apple should not throttle our utilisation.

I've put together the following which uses firebase and userNotifications, for now, it is in my app 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")
    }
}

The results are as follows:

When the app is in the foreground:

  1. I get the log "in startDownload func"

  2. I get the log "finished downloading".

When the app is in the background:

  1. I get the log "in startDownload func"

  2. I do not get the log "finished downloading".

  3. The silencer isn't working i.e. when the app is backgrounded, I am still getting the notification in the tray.

I am using Postman to send the request and tried the following payload, which results in the console error 'FIRMessaging receiving notification in invalid state 2':

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

I have the capabilities set for background fetch and remote notifications. The app is written in swift 3 and uses the latest Firebase

EDIT: Updated AppDelegate to include funcs as per comment


回答1:


A few observations:

  1. When your app is restarted by handleEventsForBackgroundURLSessionIdentifier, you have to not only save the completion handler, but you actually have to start the session, too. You appear to be doing the former, but not the latter.

    Also, you have to implement urlSessionDidFinishEvents(forBackgroundURLSession:) and call (and discard your reference to) that saved completion handler.

  2. You appear to be doing a data task. But if you want background operation, it has to be download or upload task. [You have edited question to make it a download task.]

  3. In userNotificationCenter(_:willPresent:completionHandler:), you don't ever call the completion handler that was passed to this method. So, when the 30 seconds (or whatever it is) expires, because you haven't called it, your app will be summarily terminated and all background requests will be canceled.

    So, willPresent should call its completion handler as soon it done starting the requests. Don't confuse this completion handler (that you're done handling the notification) with the separate completion handler that is provided later to urlSessionDidFinishEvents (that you're done handling the the background URLSession events).

  4. Your saved background session completion handler is not right. I'd suggest:

    var backgroundSessionCompletionHandler: (() -> Void)?
    

    When you save it, it is:

    backgroundSessionCompletionHandler = completionHandler   // note, no ()
    

    And when you call it in urlSessionDidFinishEvents, it is:

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


来源:https://stackoverflow.com/questions/41488989/using-urlsession-and-background-fetch-together-with-remote-notifications-using-f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!