インターネット接続が確立されたら、ファイルをアップロードするURLSession
の背景を使用することもできます。また、ユーザーがその時点でオンラインであっても、アップロードの進行中にアプリを離れると、アプリを離してもアプリが終了するまで続行されます(ただし、アプリを手動で削除するとホームボタン)。
残念なことに、バックグラウンドセッションは、必然的に対処するのがはるかに面倒です。重要な問題は、アップロードが完了したときにアプリが実行されていない可能性があることです。その場合にアプリを再起動すると、アップロードを作成したときに渡されたクロージャは、間に合わなくなります。つまり、完了ハンドラパターンはバックグラウンドセッションでは機能しません。 URLSession
APIのデリゲートベースのレンディションを使用する必要があります。アプリケーションデリゲートなどに追加のメソッドを実装する必要があります。ただし、「background URLSession tutorial swift 3」をgoogleに設定すると、いくつかの例が見つかります。
コメントでは、Alamofireを使用していると言います。 GET/POSTリクエストを作成するのは素晴らしいですが、バックグラウンドリクエストを処理する場合はそれほど悪くないかもしれません。
SessionManager
を作成する必要があります。
メモリからファイルをアップロードすることはできません(アプリが削除されている可能性があるため)。ファイルからアップロードする必要があります。つまり、複雑なPOSTリクエスト(JSONまたはmultipart/form-data
など)を作成している場合、これを作成してファイルに保存してからアップロードする必要があります。
あなたのアップロードの完了ハンドラに頼ることができないので、あなたがSessionManager
のdelegate
のtaskDidComplete
、dataTaskDidReceiveData
、sessionDidFinishEventsForBackgroundURLSession
など、閉鎖であなたのすべての処理をしなければなりません。例えば
、スウィフト3:
// BackgroundSession.swift
import Foundation
import Alamofire
import MobileCoreServices
import UserNotifications
class BackgroundSession {
private var sessionManager: SessionManager!
var completionHandler: (() -> Void)?
static private(set) var shared = BackgroundSession()
var responseBodies = [Int: Data]()
private init() {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
sessionManager = Alamofire.SessionManager(configuration: configuration)
// for giggles and grins, let's monitor uploads (while app is active, at least)
sessionManager.delegate.taskDidSendBodyData = { session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend in
print("\(totalBytesSent) of \(totalBytesExpectedToSend)")
}
// if app delegate captured completion handler, let's call it here
sessionManager.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
self?.completionHandler?()
self?.completionHandler = nil
}
// we probably want to capture body of response from server
sessionManager.delegate.dataTaskDidReceiveData = { [weak self] session, task, data in
if self?.responseBodies[task.taskIdentifier] == nil {
self?.responseBodies[task.taskIdentifier] = Data()
}
self?.responseBodies[task.taskIdentifier]?.append(data)
}
// what to do when task completes
//
// I'm posting `UNNotificationRequest` (in case app wasn't running when upload finished),
// but you'd probably want to post NotificationCenter so your view controller could update
// itself accordingly.
sessionManager.delegate.taskDidComplete = { [weak self] session, task, error in
var title: String
if error != nil {
print("error = \(error!)")
title = error!.localizedDescription
} else {
// parse your self?.responseBodies[task.taskIdentifier] to make sure request succeeded
title = ...
}
self?.responseBodies[task.taskIdentifier] = nil
let content = UNMutableNotificationContent()
content.title = title
content.body = "Whoo, hoo!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
}
}
@discardableResult func upload(_ data: Data, name: String, filename: String, to url: URL) throws -> UploadRequest {
// create multipart body
let multipart = MultipartFormData()
multipart.append(data, withName: name, fileName: filename, mimeType: URL(fileURLWithPath: filename).mimeType)
let fileURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(temporaryFileName())
try multipart.writeEncodedData(to: fileURL)
// create request
var request = try! URLRequest(url: url, method: .post)
request.setValue("multipart/form-data; boundary=\(multipart.boundary)", forHTTPHeaderField: "Content-Type")
// initiate upload
let uploadRequest = sessionManager.upload(fileURL, with: request)
uploadRequest.resume()
return uploadRequest
}
private func temporaryFileName() -> String {
return UUID().uuidString
}
}
extension URL {
/// Determine mime type on the basis of extension of a file.
///
/// This requires MobileCoreServices framework.
///
/// - parameter url: The file `URL` of the local file for which we are going to determine the mime type.
///
/// - returns: Returns the mime type if successful. Returns application/octet-stream if unable to determine mime type.
var mimeType: String {
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue() {
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
}
return "application/octet-stream";
}
}
そして、あなたはまた、それがバックグラウンドセッション処理のために目覚めたかどうかに目を光らせて維持するためにアプリのデリゲートを伝え、完了ハンドラをキャプチャする必要がありもしそうであれば:残念ながら
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping() -> Void) {
BackgroundSession.shared.completionHandler = completionHandler
}
、上にある、それはあなたがおそらくもっぱらに依存したくないとして、実際の実装は、どのように見えるかの総簡素化だとして毛深いですではなく、独自のUIをそれに応じて更新する必要があるかもしれないので、おそらくNotificationCenter
メッセージを処理したり、タスク識別子などを結びつけるコールバッククロージャの配列を追跡したりしているはずです。
いいアイデアです:)!私はそれについて考えなかった。 私はGETとPOSTを実行するREST APIを使って作業していて、Almofireフレームワークとコールバックを使って作業していることを伝えたいと思います。私の質問は、Almofire(コールバック付き)で電話をすれば、URLSessionと同じ結果になるのでしょうか? しかし、ユーザーがインターネットなしで複数の写真を撮ったら、それらはすべてアップロードされますか? –