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 onebackgroundPattern 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
  • URLSessionConfigurationThere are three modes, onlybackgroundMode to carry out background download.
    • defaultThe default mode is usually enough for us to use.defaultIn 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 withsessionThe same assessionWhen invalid, all content is automatically released.
    • background: Create a session that can transfer data in the background even after the APP is closed.
  • backgroundPatterns anddefaultThe pattern is very similar, exceptbackgroundThe pattern uses a single thread for data transfer.backgroundMode can be run when the program hangs, exits, or crashestask, you can also use identifiers to restore forward. Notice the backgroundSessionIt has to be uniqueidentifierSo that inAPPThe next time you run it, you can base it onidentifierTo make relevant distinctions. If the user closesAPP , iOSThe system will shut everything downbackground Session. And, after being forcibly shut down by the user,iOSThe 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 theURLSessionDownloadDelegateTo 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 inAppdelegateIn the implementationhandleEventsForBackgroundURLSessionCallback, 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 implementURLSessionDownloadDelegateThe agent’surlSessionDidFinishEventsMethod, and executed in the main threadAppDelegateBlock 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 withURLSessionThis method is called after the background transfer of the object association completes, whether the transfer completes successfully or causes an error.
  • savehandleEventsForBackgroundURLSessionmethodscompletionHandlerCallbacks, this is very important. Tell the system background download back to refresh the screen in time.
  • After cutting backstage,URLSessionDownloadDelegateThe proxy method of theTaskRelated news. When allTaskWhen all is done, the system will callAppDelegateapplication:handleEventsForBackgroundURLSession:completionHandler:The callback.
  • If theurlSessionDidFinishEventsThis proxy method does not execute saved inAppDelegateBlcok 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 classBackgroundMangerIt 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 inAppDelegateIn theapplication:handleEventsForBackgroundURLSession:completionHandler:Method callback
  • AlamofireFramework 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 processingAppDelegateThe 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 followURLSessionThe 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:

  • SessionMangerInitialize 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 worldconfigurationInitialize thesessionObject.
  • Hand over the agent by creatingSessionDelegateThis is implemented by a class that specializes in handling agentsURLSessionThe 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.
  • SessionDelegateThe agents implemented in the class areURLSessionDelegate``URLSessionTaskDelegate``URLSessionDataDelegate``URLSessionDownloadDelegate``URLSessionStreamDelegate
  • We know that when the background download task is complete, the callback is returnedurlSessionDidFinishEventsThis proxy method
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { sessionDidFinishEventsForBackgroundURLSession? (session) }Copy the code
  • To perform thesessionDidFinishEventsForBackgroundURLSessionClosure. So when is this closure assigned? becauseSessionDelegateThis is a class that deals exclusively with agents, not other logic, so this block should be a management classSessionMangerTo deal with it. This is a very important kind of design thinking.
  • The search found inSessionMangerThere’s one in the initialization methodcommonInitFunction 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 heredelegate.sessionDidFinishEventsForBackgroundURLSessionClosure declaration that will be executed as soon as the background download completes
  • The closure is called inside the main thread againbackgroundCompletionHandler, this isSessionMangerExternal functions. The closure is in theAppDelegateinsidehandleEventsForBackgroundURLSessionClosures saved in the proxy
  • Process summary:
    • First of all inAppDelegatethehandleEventsForBackgroundURLSessionMethod, put the callback closurecompletionHandlerPassed on toSessionManagerbackgroundCompletionHandlerSave it.
    • When the download is complete and comes backSessionDelegateurlSessionDidFinishEventsThe proxy calls and then executessessionDidFinishEventsForBackgroundURLSessionclosure
    • sessionDidFinishEventsForBackgroundURLSessionClosures are executed in the main threadSessionManagerbackgroundCompletionHandlerClosure, this closure isAppDelegatecompletionHandlerClosure.

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.