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, indefaultIn 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 inRAMThe life cycle of all content is the same as that ofsessionSame, so whensessionThis cached data is automatically emptied when the session is invalid.
  • background: Background session mode (this mode allows uploading and downloading in the background.)

Note:backgroundPatterns anddefaultThe pattern is very similar, butbackgroundThe pattern uses a single thread for data transfer.backgroundMode can be run when the program hangs, exits, or crashestask. Can also beAPPThe 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 oneZHBackgroundMangerSingleton 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 oneURLSessionWe already know we need to put theURLSessionConfigurationSet tobackgroundPatterns 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

  • delegateThis can be done when handling callbackssessionManagercommunicate
  • delegateReassign callback handling that does not belong to yousessionManagerTo 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 knowSessionManagerIn setting upURLSessionConfigurationThe default isdefaultMode, because the need for background download, you need to putURLSessionConfigurationThe mode is changed tobackgroundMode. Including we can also modifyURLSessionConfigurationOther 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 errorError Domain=NSURLErrorDomain Code=-999 "cancelled"
  • 3.And it willSessionManagerAfter repackaging into a singleton, inAppDelegateCan be used directly in the proxy method in.

conclusion

  • First of all inAppDelegatetheapplication:handleEventsForBackgroundURLSessionMethod, the callback closurecompletionHandlerPassed on toSessionManagerbackgroundCompletionHandler.
  • When the download is completeSessionDelegateurlSessionDidFinishEventsThe invocation of the proxy is triggeredSessionManagerthesessionDidFinishEventsForBackgroundURLSessionInvocation of proxy
  • thensessionDidFinishEventsForBackgroundURLSessionperformSessionManagerbackgroundCompletionHandlerThe closures.
  • And eventually it will come toAppDelegateapplication:handleEventsForBackgroundURLSessionIn the method ofcompletionHandlerThe 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.