Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
779 views
in Technique[技术] by (71.8m points)

swift - how to make alamofire download progress run in background ios?

I am using Alamofire to download data

How to make alamofire run download in background with swift?

Thanks

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The basic idea is as follows:

  1. The key problem is that with background downloads, your app may actually be terminated while downloads are in progress (e.g. jettisoned due to memory pressure). Fortunately, your app is fired up again when background downloads are done, but any task-level closures you originally supplied are long gone. To get around this, when using background sessions, one should rely upon session-level closures used by the delegate methods.

    import UIKit
    import Alamofire
    import UserNotifications
    
    fileprivate let backgroundIdentifier = ...
    fileprivate let notificationIdentifier = ...
    
    final class BackgroundSession {
    
        /// Shared singleton instance of BackgroundSession
    
        static let shared = BackgroundSession()
    
        /// AlamoFire `SessionManager`
        ///
        /// This is `private` to keep this app loosely coupled with Alamofire.
    
        private let manager: SessionManager
    
        /// Save background completion handler, supplied by app delegate
    
        func saveBackgroundCompletionHandler(_ backgroundCompletionHandler: @escaping () -> Void) {
            manager.backgroundCompletionHandler = backgroundCompletionHandler
        }
    
        /// Initialize background session
        ///
        /// This is `private` to avoid accidentally instantiating separate instance of this singleton object.
    
        private init() {
            let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
            manager = SessionManager(configuration: configuration)
    
            // specify what to do when download is done
    
            manager.delegate.downloadTaskDidFinishDownloadingToURL = { _, task, location in
                do {
                    let destination = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
                        .appendingPathComponent(task.originalRequest!.url!.lastPathComponent)
                    try FileManager.default.moveItem(at: location, to: destination)
                } catch {
                    print("(error)")
                }
            }
    
            // specify what to do when background session finishes; i.e. make sure to call saved completion handler
            // if you don't implement this, it will call the saved `backgroundCompletionHandler` for you
    
            manager.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] _ in
                self?.manager.backgroundCompletionHandler?()
                self?.manager.backgroundCompletionHandler = nil
    
                // if you want, tell the user that the downloads are done
    
                let content = UNMutableNotificationContent()
                content.title = "All downloads done"
                content.body = "Whoo, hoo!"
                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
                let notification = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
                UNUserNotificationCenter.current().add(notification)
            }
    
            // specify what to do upon error
    
            manager.delegate.taskDidComplete = { _, task, error in
                let filename = task.originalRequest!.url!.lastPathComponent
                if let error = error {
                    print("(filename) error: (error)")
                } else {
                    print("(filename) done!")
                }
    
                // I might want to post some event to `NotificationCenter`
                // so app UI can be updated, if it's in foreground
            }
        }
    
        func download(_ url: URL) {
            manager.download(url)
        }
    }
    
  2. Then I can just initiate those downloads. Note, I do not specify any task-specific closure when I initiate the download, but rather merely use the above session-level closures that use the details of the URLSessionTask to identify what to do:

    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // request permission to post notification if download finishes while this is running in background
    
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
                if let error = error, !granted {
                    print("(error)")
                }
            }
        }
    
        @IBAction func didTapButton(_ sender: Any) {
            let urlStrings = [
                "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
                "http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
                "http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
                "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
                "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
                "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"
            ]
            let urls = urlStrings.flatMap { URL(string: $0) }
    
            for url in urls {
                BackgroundSession.shared.download(url)
            }
        }
    
    }
    
  3. If your app isn't running when the downloads finish, iOS needs to know that, after it restarted your app, when you're all done and that it can safely suspend your app. So, in handleEventsForBackgroundURLSession you capture that closure:

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        ...
    
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            BackgroundSession.shared.saveBackgroundCompletionHandler(completionHandler)
        }
    
    }
    

    That is used by sessionDidFinishEventsForBackgroundURLSession, in step 1.

    Two observations:

    • This is only called if your app was not running when the downloads finish.

    • If doing background sessions, though, you must capture this closure and call it when you're all done processing the background session delegate methods.

So, to recap, the basic limitations of background sessions are:

  • You can only use download and upload tasks while the app is in background;

  • You can only rely upon session-level delegates because the app may have been terminated since the requests were initiated; and

  • In iOS, you must implement handleEventsForBackgroundURLSession, capture that completion handler, and call it when your background process is done.

I must also point out that while Alamofire is a wonderful library, it's not actually adding a lot value (above and beyond what is provided by URLSession to this background download process). If you're doing simple uploads/downloads only, then you might consider just using URLSession directly. But if you are using Alamofire in your project already or if your requests consist of more complicated application/x-www-form-urlencoded requests (or whatever) which merit the advantages of Alamofire, then the above outlines the key moving parts involved in the process.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...