Leave a rating/review
Notes: 04. Background Downloads
You’ve seen that you can download and upload files, and these transfers are done in a background thread. This means your app can respond to user interface events.
URLSession supports background transfers while your app is suspended as long as it’s created using a background session configuration object that has an identifier.
This session’s tasks run the same as non-background sessions while the app is running, but they can continue to run when the app is suspended.
Let’s take a look at how to implement background downloads. Open the Starter project for this episode and go to the MutableSongDownloader.swift file.
Replace your URLSession property with the following:
private var session: URLSession
And then add the following initializer:
override init() {
super.init()
let identifier = "com.razeTunes.mutableSongDownloader"
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
This initializes your URLSession with a background configuration using your own identifier.An error comes up in Xcode about self.session not being initialized before you call super.init(). To fix that, update your URLSession property so it’s implicitly unwrapped:
private var session: URLSession!
Next, create a new file called AppDelegate.swift. Add this code:
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
}
You create a class called AppDelegate that inherits from NSObject and implements the UIApplicationDelegate method.
You’ll use this class to conform to the necessary delegate methods in order to handle your background sessions. Over in AppMain.swift, add the following property:
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
This attribute is used to receive app delegate call backs when your app uses is a SwiftUI app. The attribute has you specify your custom type that conforms to UIApplication delegate. Back in AppDelegate.swift, add this property:
class AppDelegate: NSObject, UIApplicationDelegate {
var backgroundCompletionHandler: (() -> Void)?
}
This will keep a completion handler for your background session if necessary. Add the code to implement the application delegate method to handle background URLSession events:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
print("URLSession identifier: \(identifier)")
backgroundCompletionHandler = completionHandler
}
For now you print out an identifier just for reference, and stores the completion handler in your property. Feel free to clean up the print statement now, or later, if you don’t need it anymore.
Switch over to MutableSongDownloader.swift. Add this static constant:
static let BackgroundSongDownloadDidFinish =
NSNotification.Name(rawValue: "BackgroundSongDownloadDidFinish")
You’ll use this notification to communicate for any events that happen between the app delegate and your song downloader. In the extension for the URLSession delegate, add this method:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
Task { @MainActor in
print("urlSessionDidFinishEvents called.")
NotificationCenter.default.post(name: Self.BackgroundSongDownloadDidFinish, object: nil)
}
}
This method is an optional method of URLSessionDelegate that gets called when all events have been delivered for your background URLSession.
So the sequence is for your app delegate to get informed about any events that need to be handled, like for when a transfer completes or a request that needs your app to provide authentication, and once these events are delivred and handled, your URLSessionDelegate is called.
Inside, and once again for testing and debugging purposes, you are printing out to the console and posting the notification you just defined in the static constant.
Back in the app delegate, add the code for another of the delegate methods:
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
NotificationCenter.default.addObserver(self,
selector: #selector(backgroundSongDidDownload),
name: MutableSongDownloader.BackgroundSongDownloadDidFinish,
object: nil)
return true
}
This method gets called when your application finishes launching, and it’s where you’ll listen to the notification that is being posted from MutableSongDownloader.
The error you get after adding this method is because you need to define a method called backgroundSongDidDownload. Do that next within the AppDelegate class:
@objc private func backgroundSongDidDownload() {
if let backgroundCompletionHandler = backgroundCompletionHandler {
backgroundCompletionHandler()
}
}
This method checks whether there is a completion handler stored in your property and, if so, calls it to tell the OS that you’ve finished handling all of your events.
In youu scenario, all events will be handled by the other delegate methods and work done in MutableSongDownloader, so this method is to comply with the API’s requirement that you call the completion handler when all the work is done for the background event you got informed about.
Back in MutableSongDownloader, there’s a minor update needed in the delegate method that gets called when your download finished.
In the first guard statement, remove the line that creates the lastPathComponent constant:
guard let documentsPath = fileManager.urls(for: .documentDirectory,
in: .userDomainMask).first
else {
Task {
await MainActor.run {
state = .failed
}
}
return
}
And move it to just above the line that creates the destinationURL:
let lastPathComponent = downloadURL?.lastPathComponent ?? "Song.m4a"
You do this because there might not be a downloadURL in the scenario that this is a background download, so you give your song a default name.
Testing Background Downloads
And with that, you’re done and have implemented the ability to perform background downloads. Build and run your app. Tap the download button and background your app.
Maybe the result is what you expected, but it’s possible it isn’t. Testiing this can be a little problematic.
It’s up to the operating system when backgorund transfers are performed and with what priority. There is no guarantee that your song will be downloaded within a certain amount of time since the app was background.
If the download is happening too fast for you to even background it, which is another tricky scenario in testing this, then you can leverage the Network Link Conditioner.
And with that, another great and exciting feature has been implemented!
The process of performing background download resumes to:
- Add any necessary properties to track your background session state, and to be able to recreate it if needed.
-
Implement the
UIApplicationDelegatemethod to handle any background events. -
Implement the
URLSessionDelegatemethod for when the download is finished.
With a good networking stack in place, it wasn’t too long or difficult to add this functionality. As a user, however, it’s something that can greatly improve the experience.
In the next episode you’ll learn about Sockets. I’ll see you in a bit! :)