Alamofireを使用してデータをダウンロードしています
アラモファイアを迅速にバックグラウンドでダウンロードするにはどうすればよいですか?
ありがとう
基本的な考え方は次のとおりです。
主な問題は、バックグラウンド ダウンロードの場合、ダウンロードの進行中にアプリが実際に終了する可能性があることです (たとえば、メモリ プレッシャが原因で破棄されます)。幸いなことに、バックグラウンドでのダウンロードが完了すると、アプリは再び起動されますが、最初に指定したタスク レベルのクロージャはすべて失われています。これを回避するには、バックグラウンド セッションを使用するときに、デリゲート メソッドによって使用されるセッション レベルのクロージャーに依存する必要があります。
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)
}
}
その後、それらのダウンロードを開始できます。ダウンロードを開始するときにタスク固有のクロージャーを指定していないことに注意してください。むしろ、の詳細を使用してURLSessionTask
何をすべきかを特定する上記のセッションレベルのクロージャーを使用するだけです。
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)
}
}
}
ダウンロードが完了したときにアプリが実行されていない場合、iOS は、アプリを再起動した後、すべての作業が完了し、アプリを安全に一時停止できることを知る必要があります。したがって、handleEventsForBackgroundURLSession
その閉鎖をキャプチャします。
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
BackgroundSession.shared.saveBackgroundCompletionHandler(completionHandler)
}
}
これはsessionDidFinishEventsForBackgroundURLSession
、ステップ 1 で によって使用されます。
2 つの観察:
これは、ダウンロードが終了したときにアプリが実行されていなかった場合にのみ呼び出されます。
ただし、バックグラウンド セッションを実行する場合は、このクロージャをキャプチャして、バックグラウンド セッション デリゲート メソッドの処理がすべて完了したときに呼び出す必要があります。
要約すると、バックグラウンド セッションの基本的な制限は次のとおりです。
アプリがバックグラウンドにある間は、ダウンロード タスクとアップロード タスクのみを使用できます。
リクエストが開始された後にアプリが終了した可能性があるため、セッション レベルのデリゲートにのみ依存できます。と
iOS では、 を実装しhandleEventsForBackgroundURLSession
、その完了ハンドラーをキャプチャして、バックグラウンド プロセスが完了したときにそれを呼び出す必要があります。
また、Alamofire は素晴らしいライブラリですが、実際には多くの価値を追加しているわけではないことも指摘しておく必要があります (URLSession
このバックグラウンド ダウンロード プロセスによって提供される以上の価値があります)。単純なアップロード/ダウンロードのみを行っている場合は、URLSession
直接使用することを検討してください。ただし、プロジェクトで既に Alamofire を使用している場合、またはapplication/x-www-form-urlencoded
Alamofire の利点に値するより複雑な要求 (またはその他) で構成されている要求である場合、上記はプロセスに関連する主要な可動部分の概要を示しています。