preface
URLSession is a key class that responds to sending and receiving HTTP requests. First create a simple downloadTask using the global urlsession.shared and downloadTask:
let url = URL(string: "https://mobileappsuat.pwchk.com/MobileAppsManage/UploadFiles/20190719144725271.png")
let request = URLRequest(url: url!)
let session = URLSession.shared
letdownloadTask = session.downloadTask(with: request, completionHandler: { (location:URL? , response:URLResponse? , error:Error?) -> Voidin
print("location:\(location)")
letlocationPath = location! .pathlet documnets:String = NSHomeDirectory() + "/Documents/1.png"
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
print("new location:\(documnets)")
})
downloadTask.resume()
Copy the code
Can see the download here is the front desk to download, that is to say, if the program back to the background (e.g., press the home button, or cut into other applications), the current download task will stop immediately, this kind words for some of the larger files, download users unable to switch to the background in the process, not too friendly for users is a kind of experience. Let’s take a look at the implementation of downloading in the background:
URLsession Background download
The URLSessionConfiguration class has three modes for creating URLSession instances:
URLSessionConfiguration has three modes as follows:
default
: Default session mode (which uses a disk cache-based persistence strategy and is usually the most used, indefault
In this mode, the system creates a persistent cache and stores the certificate in the user’s keystring.ephemeral
: Temporary session mode (this mode does not use disk to save any data. It’s stored inRAM
The life cycle of all content is the same as that ofsession
Same, so whensession
This cached data is automatically emptied when the session is invalid.background
: Background session mode (this mode allows uploading and downloading in the background.)
Note:
background
Patterns anddefault
The pattern is very similar, butbackground
The pattern uses a single thread for data transfer.background
Mode can be run when the program hangs, exits, or crashestask
. Can also beAPP
The next time you start up, use the identifier to resume the download.
Create a background session and specify an identifier:
let urlstring = URL(string: "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg")! // Step 1: Initialize a background session configurationlet configuration = URLSessionConfiguration.background(withIdentifier: "com.Henry.cn") // Step 2: Initialize a session session according to the configurationletsession = URLSession.init(configuration: configuration, delegate: self, delegateQueue: Operationqueue.main) // step 3: pass in URL, create downloadTask downloadTask, start downloading session. DownloadTask (with: URL).resume()Copy the code
The next session of the download agent URLSessionDownloadDelegate, URLSessionDelegate method:
The extension ViewController: URLSessionDownloadDelegate {/ / download agent method, download end func urlSession (_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo Location: URL) {// Download complete - Start sandbox migrationprint("Download complete address - \(location)")
letLocationPath = location.path // Copy to user directorylet documnets = NSHomeDirectory() + "/Documents/" + "com.Henry.cn" + ".dmg"
print("Move to new Address :\(Documnets)"// Create a file managerletfileManager = FileManager.default try! FileManager. MoveItem (atPath: locationPath, toPath: Documnets)} // Download proxy method, monitor 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
After set the code, still cannot achieve the purpose of the background download, you also need to open the background in the AppDelegate download permissions, realize handleEventsForBackgroundURLSession method:
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
After the application is switched to the background, the Session interacts with the ApplicationDelegate, and the tasks in the session continue to download. When all tasks have completed (whether the download has failed or succeeded), The application system will be called ApplicationDelegate: handleEventsForBackgroundURLSession: completionHandler: callback, after processing events, The closure is executed in the completionHandler parameter so that the application can get a refresh of the user interface.
If we view the handleEventsForBackgroundURLSession this API, will find that apple documentation requirements in realize the need to implement URLSessionDidFinishEvents agent after the download is complete, in order to update the screen.
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("Background task")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
Copy the code
If this method is not implemented ⚠️️ : The implementation of background download does not affect the application, but may cause stuttering or frame drop during the application switching from background to foreground, and may print a warning on the console:
Alamofire background download
Through the example above 🌰 will find that if you want to achieve a background download, need to write a lot of code, but also pay attention to the background download permissions to open, the implementation of the callback after the download is complete, missed any step, the realization of the background download may not be perfect, the following is to compare, is how to realize the background in the Alamofire download.
Create a backend download manager class for ZHBackgroundManger:
struct ZHBackgroundManger {
static let shared = ZHBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.Henry.AlamofireDemo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "com.Henry.AlamofireDemo"
return SessionManager(configuration: configuration)
}()
}
Copy the code
Implementation of background download:
ZHBackgroundManger.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
And do the same thing in the AppDelegate:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
ZHBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
Copy the code
There may be questions here 🤔, why create oneZHBackgroundManger
Singleton class?
So the following with this question ❓ to explore
If click ZHBackgroundManger Shared. Manager. The download manager will find this is our SessionManager here, so just like in the source code of our SessionManager look at:
SessionManager
default
URLSessionConfiguration
SessionManager
So let’s look at the SessionManager initialization method:
SessionManager
init
URLSessionConfiguration
default
In the previous part of the content, create oneURLSession
We already know we need to put theURLSessionConfiguration
Set tobackground
Patterns do.
There is also a session delegate delegate in the initialization method, and the delegate is passed into the session as its delegate, and the session initialization is such that future callbacks will be handled by self.delegate. The SessionManager instance creates a SessionDelegate object to handle the different types of proxy callbacks generated by the underlying URLSession. (This is also known as a proxy transfer).
After the broker is handed over, some additional configuration information is done in the commonInit() method:
delegate.sessionManager
self
self
delegate
delegate
sessionManager
weak
And the reason why I’m going to write delegate.sessionManager = self this way is
delegate
This can be done when handling callbackssessionManager
communicatedelegate
Reassign callback handling that does not belong to yousessionManager
To redistribute- Reduce dependence on other logical content
And the delegate here. SessionDidFinishEventsForBackgroundURLSession closure, as long as background tasks the download is complete will callback to inside the closure, inside the closure, the callback for the main thread, Call the backgroundCompletionHandler, this also is in the AppDelegate application: the completionHandler handleEventsForBackgroundURLSession method. At this point, the SessionManager flow looks like this.
For the above question:
- 1.Through the source code we can know
SessionManager
In setting upURLSessionConfiguration
The default isdefault
Mode, because the need for background download, you need to putURLSessionConfiguration
The mode is changed tobackground
Mode. Including we can also modifyURLSessionConfiguration
Other configurations - 2.At download time, the application enters the background download, and if the above configuration is not made into a singleton, or if it is not held, it will be released after entering the background, resulting in an error
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
- 3.And it will
SessionManager
After repackaging into a singleton, inAppDelegate
Can be used directly in the proxy method in.
conclusion
- First of all in
AppDelegate
theapplication:handleEventsForBackgroundURLSession
Method, the callback closurecompletionHandler
Passed on toSessionManager
的backgroundCompletionHandler
. - When the download is complete
SessionDelegate
的urlSessionDidFinishEvents
The invocation of the proxy is triggeredSessionManager
thesessionDidFinishEventsForBackgroundURLSession
Invocation of proxy - then
sessionDidFinishEventsForBackgroundURLSession
performSessionManager
的backgroundCompletionHandler
The closures. - And eventually it will come to
AppDelegate
的application:handleEventsForBackgroundURLSession
In the method ofcompletionHandler
The call.
This is the analysis of the code for Alamofire background download. In fact, through the source code, it is found that the principle of background download using URLSession is roughly the same, but the use of Alamofire makes the code look more brief, and there will be a lot of default configuration in Alamofire. We only need to modify the required configuration items.