问题
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:
I get the log "in startDownload func"
I get the log "finished downloading".
When the app is in the background:
I get the log "in startDownload func"
I do not get the log "finished downloading".
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:
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.
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.]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 tourlSessionDidFinishEvents
(that you're done handling the the backgroundURLSession
events).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