preface
In order to improve the user experience, network requests such as downloading large files are often placed in the background so that tasks can continue even when the user enters the background. So this article will discuss how to use the API of Apple’s native URLSession framework and the third-party framework Alamofire based on URLSession to achieve the background download function.
Background download through URLSession
First, initiate onebackground
Pattern request:
// Initializes a background pattern configuration. Identifier: the unique Identifier of the configuration objectlet configuration = URLSessionConfiguration.background(withIdentifier: "com.test"// Create sessionletsession = URLSession.init(configuration: configuration, delegate: self, delegateQueue: Resume starts session. DownloadTask (with: URL(string: urlStr).resume().Copy the code
URLSessionConfiguration
There are three modes, onlybackground
Mode to carry out background download.default
The default mode is usually enough for us to use.default
In this mode, the system creates a persistent cache and stores the certificate in the user’s keystring.ephemeral
: The system does not have any persistent storage, and the life cycle of all content is associated withsession
The same assession
When invalid, all content is automatically released.background
: Create a session that can transfer data in the background even after the APP is closed.
background
Patterns anddefault
The pattern is very similar, exceptbackground
The pattern uses a single thread for data transfer.background
Mode can be run when the program hangs, exits, or crashestask
, you can also use identifiers to restore forward. Notice the backgroundSession
It has to be uniqueidentifier
So that inAPP
The next time you run it, you can base it onidentifier
To make relevant distinctions. If the user closesAPP
,iOS
The system will shut everything downbackground Session
. And, after being forcibly shut down by the user,iOS
The system does not actively wake upAPP
, only the user booted up next timeAPP
, data transfer will continue.
Implement related proxy callback
The extension ViewController: URLSessionDownloadDelegate {/ / download complete callback func urlSession (_ session: urlSession downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {let locationPath = location.path
let documnets = NSHomeDirectory() + "/Documents/" + "\(Date().timeIntervalSince1970)" + ".mp4"
letFileManager = filemanager.default // Copy the file to the user directory try! FileManager. MoveItem (atPath: locationPath, toPath: Documnets)} // Listen for download progress. Func urlSession(_ session: urlSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
print(Download progress: \ "(Double (totalBytesWritten)/Double (totalBytesExpectedToWrite)) \ n")}}Copy the code
- To achieve the
URLSessionDownloadDelegate
To listen to and process the downloaded data.
Handle the background download callback closure in the AppDelegate
- When only the first two steps are implemented, the background download function cannot be implemented. This is because a very important step has been missed.
- First of all in
Appdelegate
In the implementationhandleEventsForBackgroundURLSession
Callback, and save to complete the block.
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? / / used to hold the background download completionHandler var backgroundSessionCompletionHandler: (() - > Void)? func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { self.backgroundSessionCompletionHandler = completionHandler } }Copy the code
- And then you need to implement
URLSessionDownloadDelegate
The agent’surlSessionDidFinishEvents
Method, and executed in the main threadAppDelegate
Block saved in. Note that the thread switches the main thread because the interface is refreshed.
extension ViewController:URLSessionDownloadDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("Background task download back")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
}
Copy the code
- Complete the above steps to achieve the background download function.
- Application in all with
URLSession
This method is called after the background transfer of the object association completes, whether the transfer completes successfully or causes an error. - save
handleEventsForBackgroundURLSession
methodscompletionHandler
Callbacks, this is very important. Tell the system background download back to refresh the screen in time. - After cutting backstage,
URLSessionDownloadDelegate
The proxy method of theTask
Related news. When allTask
When all is done, the system will callAppDelegate
的application:handleEventsForBackgroundURLSession:completionHandler:
The callback. - If the
urlSessionDidFinishEvents
This proxy method does not execute saved inAppDelegate
Blcok inside will cause the page to be stuck when entering the foreground, affecting the user experience and printing warning information.
Background download through Alamofire
Create a download task
BackgroundManger.shared.manager
.download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
letfileUrl = documentUrl? .appendingPathComponent(response.suggestedFilename!)return(fileUrl! ,[.removePreviousFile,.createIntermediateDirectories]) } .response { (downloadResponse)in
print("Download callback information: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("Download Progress: \(progress))}Copy the code
- There is a special package for backend download management class
BackgroundManger
It is a singleton. If you don’t allocate it to simple interest, or not to be held. It will be released after entering the background, and the network will report an error:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
- Singletons are easy to receive directly in
AppDelegate
In theapplication:handleEventsForBackgroundURLSession:completionHandler:
Method callback Alamofire
Framework using chain programming, write up very convenient concise, logical clear, readable.
struct BackgroundManger {
static let shared = BackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.text.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
Copy the code
Second, the processingAppDelegate
The callback
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
BackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
Copy the code
- The function of this step is to follow
URLSession
The implementation of the role of background download. This line of code is very important, must be added, otherwise after downloading into the foreground, there will be frame drop situation, affect the user experience.
Alamofire is very simple to use, so how does it help us deal with some tedious things? Let’s analyze it together:
SessionManger
Initialize the
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
- First came in through the outside world
configuration
Initialize thesession
Object. - Hand over the agent by creating
SessionDelegate
This is implemented by a class that specializes in handling agentsURLSession
The agent. In this way, business sink can be achieved, and it is easy to read and decouple. Each class only needs to be responsible for its own tasks, with a clear division of labor and no overstaffing. SessionDelegate
The agents implemented in the class areURLSessionDelegate``URLSessionTaskDelegate``URLSessionDataDelegate``URLSessionDownloadDelegate``URLSessionStreamDelegate
- We know that when the background download task is complete, the callback is returned
urlSessionDidFinishEvents
This proxy method
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { sessionDidFinishEventsForBackgroundURLSession? (session) }Copy the code
- To perform the
sessionDidFinishEventsForBackgroundURLSession
Closure. So when is this closure assigned? becauseSessionDelegate
This is a class that deals exclusively with agents, not other logic, so this block should be a management classSessionManger
To deal with it. This is a very important kind of design thinking. - The search found in
SessionManger
There’s one in the initialization methodcommonInit
Function call
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
- This is the proxy right here
delegate.sessionDidFinishEventsForBackgroundURLSession
Closure declaration that will be executed as soon as the background download completes - The closure is called inside the main thread again
backgroundCompletionHandler
, this isSessionManger
External functions. The closure is in theAppDelegate
insidehandleEventsForBackgroundURLSession
Closures saved in the proxy - Process summary:
- First of all in
AppDelegate
thehandleEventsForBackgroundURLSession
Method, put the callback closurecompletionHandler
Passed on toSessionManager
的backgroundCompletionHandler
Save it. - When the download is complete and comes back
SessionDelegate
的urlSessionDidFinishEvents
The proxy calls and then executessessionDidFinishEventsForBackgroundURLSession
closure sessionDidFinishEventsForBackgroundURLSession
Closures are executed in the main threadSessionManager
的backgroundCompletionHandler
Closure, this closure isAppDelegate
的completionHandler
Closure.
- First of all in
conclusion
Alamofire encapsulates URLSession, so the principle of background download in these two ways is the same. However, Alamofire is more simple and convenient to use, relying on sinking, network layer sinking. You can also refer to this design idea when writing your SDK or project.
If you have any questions or suggestions, you are welcome to comment or write to us. Like friends can click on the following and like, the follow-up will continue to update the article.