When implementing background uploads on iOS, I found this incredibly helpful article by Antoine van der Lee. Any feature that relies on uncontrollable iOS system behavior is a tough one to implement, and his article was my map through the void. Since it was published, some things have changed slightly in iOS, so Iām writing this post to add some corrections about implementing background uploads in iOS.
Invalidating URLSession
One tip it gives at the end is to invalidate the URLSession on app launch so that you can start from a clean slate:
Starting from scratch
First of all, itās good to start with a clean slate. We can do this by executing the following piece of code on launch to invalidate our URLSession and its tasks:
#if DEBUG if debuggingBackgroundTasks { /// Cancel any running tasks and invalidate the session. URLSession.shared.invalidateAndCancel() } #endif
Iām not sure this is a good idea, or at the very least requires relaunching your app after invalidating all tasks. The issue is that this is invalidating URLSession.shared, not the background URLSession we created to handle background uploads.
The Apple Documentation for URLSession.invalidateAndCancel() states:
Calling this method on the session returned by theĀ
sharedĀ method has no effect.
If we invalidate our background URLSession instead, we run into a new problem: Only a single URLSession for a given configuration identifier will ever be created. This means that if we invalidate and then re-create our background URLSession, we actually get back the same URLSession that we just invalidated!
backgroundSession.invalidateAndCancel()
let config = URLSessionConfiguration.background(withIdentifier: Self.backgroundSessionIdentifier)
config.sessionSendsLaunchEvents = true
config.isDiscretionary = false
backgroundSession = URLSession(configuration: config) // <--- This returns the same `backgroundSession` that we just invalidated!
The Apple Documentation for URLSession.invalidateAndCancel() states:
Once invalidated, references to the delegate and callback objects are broken. After invalidation, session objects cannot be reused.
This does mention we shouldnāt use the same session, but doesnāt mention that the exact same URLSession will be returned from the init(configuration:), which I found surprising.
Instead of invalidating the session to clear out old upload tasks, simply iterate through the tasks and cancel them individually:
let tasks = await yourBackgroundURLSession.allTasks
for task in tasks {
task.cancel()
}
Forcing App Background
One other note, in the article Antoine recommends calling exit(0) from applicationDidEnterBackground(:_:)
Make your app suspend sooner
Waiting for your app to suspend is a waste of time. By usingĀ
exit(0)Ā we can force our app to suspend:func applicationDidEnterBackground(_ application: UIApplication) { #if DEBUG if debuggingBackgroundTasks { /// Exit to make our app suspend directly. exit(0) } #endif }
However, the Apple documentation for that method notes that applicationDidEnterBackground(_:) will not be called for applications using scenes:
If youāre using scenes (seeĀ Scenes), UIKit will not call this method. UseĀ
sceneĀ instead to perform any final tasks. UIKit posts aĀDid Enter Background(_:) didĀ regardless of whether your app uses scenes.Enter Background Notification
Oops! Iād completely forgotten about this, and I was left wondering why my app delegate method wasnāt call. The fix is easy, I switched to use in your scene delegate.
sceneDidEnterBackground(_ scene: UIScene)
Conclusion
If you want to cancel all background tasks and start from scratch, call cancel() on your background URLSession.allTasks. Otherwise, if you invalidate your background session, the background session will not be able to be re-used in the same app session that it was invalidated in. To start new background tasks, you would need to relaunch the app and not invalidateAndCancel() a second time.