The last article mentioned background downloads, so let’s look at how background downloads are handled in Alamofire. First, use native writing method to achieve a background download task, using Alamofire to achieve, through comparison to see the advantages of Alamofire.
Source address: testapi. Onapp. Top/public/vide…
URLSession background download
First you need to create a session and set the session parameters:
//1. Set request parameters
let configuration = URLSessionConfiguration.background(withIdentifier: "com.yahibo.background_id")
let session = URLSession.init(configuration: configuration,delegate: self,delegateQueue: OperationQueue.main)
//2. Set the data source
let videoUrl = "http://onapp.yahibo.top/public/videos/video.mp4"
let url = URL.init(string: videoUrl)!
//3, create a download task and initiate the request
session.downloadTask(with: url).resume()
Copy the code
- Configure the session as
background
Mode to enable the background download function - Create a download task and execute it
resume
Start the task - After session initialization, the task callback only goes through the proxy method, and the data callback is not performed through the closure. If the closure is used, an error message will be displayed
session.downloadTask(with: url) { (url, response, error) in
print(url)
print(response)
print(error)
}.resume()
Copy the code
Error Message: Completion handler blocks are not supported in background sessions. Use a delegate instead.Copy the code
Block callbacks to data are not supported in background sessions and proxies are required, so in background downloads we use proxy methods directly to process data. The proxy method is as follows:
extension Alamofire2Controller: URLSessionDownloadDelegate{
//1. Download progress
func urlSession(_ session: URLSession.downloadTask: URLSessionDownloadTask.didWriteData bytesWritten: Int64.totalBytesWritten: Int64.totalBytesExpectedToWrite: Int64) {
print("Download progress:\(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))")}// The download is complete
func urlSession(_ session: URLSession.downloadTask: URLSessionDownloadTask.didFinishDownloadingTo location: URL) {
let locationPath = location.path
print("Download completed:\(location.path)")
// Store to user directory
let documents = NSHomeDirectory(a)+ "/Documents/my.mp4"
print("Storage location:\(documents)")
// Copy the video to the destination address
let fileManager = FileManager.default
try!fileManager.moveItem(atPath: locationPath, toPath: documents)
}
}
Copy the code
When the file is downloaded, it will be saved in the TMP file in the sandbox first. This file only stores temporary data and will be automatically cleaned after use. Therefore, you need to copy files downloaded in TMP to the Documents folder for storage.
Check the file download status through the printed path. The above operations do not actually complete the background download. When the application is returned to the background, the download task is stopped.
Download Progress:0.3653140762324527Download Progress:0.4018703091059228
2019-08-19 15:23:14.237923+0800 AlamofireDemo[849:9949] An error occurred on the xpc connection requesting pending callbacks for the background session: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.nsurlsessiond" UserInfo={NSDebugDescription=Connection to service named com.apple.nsurlsessiond}/Users/hibo/Library/Developer/CoreSimulator/Devices/404EDFDD-735E-454B-A576-70268D8A17C0/data/Containers/Data/Application/E3175312-D6B8-4576-9B84-4EBD7751A4C0/Library/Caches/com.apple.nsurlsessiond/Downloads/com.yahibo.background_id/CFNetworkDownload_eo4RMO.tmp Storage location:/Users/hibo/Library/Developer/CoreSimulator/Devices/404EDFDD-735E-454B-A576-70268D8A17C0/data/Containers/Data/Application/E3175312-D6B8-4576-9B84-4EBD7751A4C0/Documents/20190819152314.mp4
Copy the code
As mentioned in the previous article, Apple officially requires that two agent methods be implemented during background task downloads to notify the system of updates to the interface.
1. Implement in an AppDelegate
var backgroundCompletionHandler: (()->Void)? = nil
// Set this to enable background download permission
func application(_ application: UIApplication.handleEventsForBackgroundURLSession identifier: String.completionHandler: @escaping() - >Void) {
self.backgroundCompletionHandler = completionHandler
}
Copy the code
- Enable the background download permission, the implementation of proxy method is open
Implement the proxy method in the Alamofire2Controller extension above
// Background task download callback
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("Background task download back")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate.let backgroundHandle = appDelegate.backgroundCompletionHandler else { return }
backgroundHandle()
}
}
Copy the code
- This method is called when the background task completes, and is called inside the method
AppDelegate
In, notifying the system to update the interface or drop frames
Add the above method to run the download again, exit the foreground, and wait a few seconds to see that there is a background download complete callback printed on the console. In this case, we go back to the foreground and our page has actually been updated. At this point we have completed a background download function.
Summary: The background download task requires the implementation of four proxy methods
Controller:
URLSessionDownloadTask:
Get download progressDidFinishDownloadingTo:
Download complete process the downloaded fileUrlSessionDidFinishEvents:
After the background download is completed, the system is prompted to update the interface in time and execute the closure function in Application
Application:
BackgroundCompletionHandler:
The background download completes the closure for receiving notification messages
From years of development experience (too loaded 😂), the above implementation is not the ideal result, functional code dispersion. Here’s how Alamofire is implemented.
Second, Alamofire background download
Alamofire.request(url,method: .post,parameters: ["page":"1"."size":"20"]).responseJSON {
(response) in
switch response.result{
case .success(let json):
print("json:\(json)")
break
case .failure(let error):
print("error:\(error)")
break}}Copy the code
In the above code, Alamofire can send requests directly through Request, and there is also a download method in the framework to complete the download task. Check the official documentation.
// Download the file
Alamofire.download(url, to: { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("\ [self.currentDateStr()).mp4")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
})
.downloadProgress { (progress) in
print(progress)
}.response(queue: DispatchQueue.global(qos: .utility), completionHandler: { (response) in
print("Download complete:\(response)")})Copy the code
DownloadRequest. DownloadOptions:
Set the location where the downloaded files are storedDownloadProgress:
Get download progress
Although the above files can be downloaded, they cannot be downloaded in the background. First of all, officials point out:
The Alamofire.download APIs should also be used if you need to download data while your app is in the background. For more information, please see the Session Manager Configurations section.
We need to manually configure the session to be in background mode, but the download used above actually uses the default mode and does not support background download. The following code:
public static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
Copy the code
Looking at the official documentation and source code, we actually only need to reset the session configuration information.
Changing the Session Mode
let configuration = URLSessionConfiguration.background(withIdentifier:"com.yahibo.background_id")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
sessionManager = SessionManager(configuration: configuration)
Copy the code
The above sessionManager needs to be set up as a singleton to receive Appdelegate’s proxy closure functions in background download mode and use the closure to inform the system to update the interface. The code is as follows:
struct BackgroundManager {
static let shared = BackgroundManager(a)let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier:"com.yahibo.background_id")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
Copy the code
Let’s start the download function:
BackgroundManager.shared.manager.download(url) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("\ [self.currentDateStr()).mp4")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { (progress) in
print(progress)
}.response(queue: DispatchQueue.global(qos: .utility), completionHandler: { (response) in
print("Download complete:\(response)")})Copy the code
- Same as above direct call
download
Method to download and store data
We also need to call apple should request handleEventsForBackgroundURLSession block of code in the notification system update interface, how to make the connection in our SessionManager. The code is as follows:
// Set this to enable background download permission
func application(_ application: UIApplication.handleEventsForBackgroundURLSession identifier: String.completionHandler: @escaping() - >Void) {
BackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
Copy the code
SessionManager
We have everything we needbackgroundCompletionHandler
Code block declaration to receive the closure and invoke the closure
Simple steps to achieve the background we want to download the function, simple coding, clear logic. Here we only realized on the background in Application download authority of agency, but not in the controller to set the delegate and realize urlSessionDidFinishEvents agent method, Here it’s not hard to guess URLSessionDownloadTask, didFinishDownloadingTo, urlSessionDidFinishEvents agent method should be implemented in our SessionManager, Unified management is then passed back to the current interface in the form of closures. Let’s see if SessionManager is implemented this way.
SessionManager source code exploration
First follow the creation of the SessionManager to find the class initialization method:
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
Copy the code
Initialization takes three initial parameters with default values and returns a new SessionManager object. In the background download above we only configured the configuration parameter, set to background download mode. As mentioned above, there should be a proxy implementation in the SessionManager. In this function, we see that a SessionDelegate object is initialized and the URLSession proxy implementation points to the SessionDelegate object. It’s not hard to guess that the urlSession-related delegate methods should all be implemented in the SessionDelegate class.
SessionDelegate
In sessionDelegate. swift, the SessionDelegate inherits from NSObject and declares all the closure functions associated with the URLSession proxy to return the results of the proxy event to the interface.
The following proxy methods are implemented in the extension method:
URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
URLSessionStreamDelegate
Copy the code
Let’s take a look at what is implemented inside the agent methods associated with downloading. The code is as follows:
extension SessionDelegate: URLSessionDownloadDelegate {
open func urlSession(
_ session: URLSession.downloadTask: URLSessionDownloadTask.didFinishDownloadingTo location: URL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
}
open func urlSession(
_ session: URLSession.downloadTask: URLSessionDownloadTask.didWriteData bytesWritten: Int64.totalBytesWritten: Int64.totalBytesExpectedToWrite: Int64)
{
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(
session,
downloadTask: downloadTask,
didWriteData: bytesWritten,
totalBytesWritten: totalBytesWritten,
totalBytesExpectedToWrite: totalBytesExpectedToWrite
)
}
}
open func urlSession(
_ session: URLSession.downloadTask: URLSessionDownloadTask.didResumeAtOffset fileOffset: Int64.expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(
session,
downloadTask: downloadTask,
didResumeAtOffset: fileOffset,
expectedTotalBytes: expectedTotalBytes
)
}
}
}
Copy the code
The above three methods are used to monitor the download progress and completion of the download, and within the callback the closure callback agent event to the main screen. This file implements all of the methods of the proxy mentioned above, passing values to the outside world through declared closures that are only called externally. The closure function here, bridged with the outside world, returns a self, so it can retrieve the data passed by the agent as a chain. As follows:
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
downloadDelegate.progressHandler = (closure, queue)
return self
}
Copy the code
- Bridge interface and interior
SessionDelegate
Extension agent, complete download progress monitoring
Other bridging methods are omitted…
We found an extension from URLSessionDelegate for background downloads:
extension SessionDelegate: URLSessionDelegate {
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
}
Copy the code
After the background download is complete, the method is executed. In this method, the external implementation closure is called, which is implemented in the SessionManager as follows:
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?). {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession ={[weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?()}}}Copy the code
- to
SessionDelegate
The incomingself
, circular reference appears here, heredelegate.sessionManager
useweak
Modification to solve - implementation
delegate
The mid-background download completion callback closure is where to receive the background download completion message - In the main thread, call
backgroundCompletionHandler
Sends the message tobackgroundCompletionHandler
Closure implementation of
Should be clear, here is our SessionManager backgroundCompletionHandler statement of closures, get closure system to realize in the Application, used for communication with the system, tell the system update interface in the background.