5

バックグラウンドでオブジェクトをアップロードできるようにURLSessionDelegate、 、URLSessionTaskDelegate、およびを (ほぼ) 正常に実装しました。URLSessionDataDelegateしかし、サーバーが戻ったときに送信したオブジェクトを削除できるように、完了ハンドラーを実装する方法がわかりませんstatuscode=200

私は現在、uploadTaskこのように始めます

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.myObject\(myObject.id)")
let backgroundSession = URLSession(configuration: configuration, 
                                   delegate: CustomDelegate.sharedInstance, 
                                   delegateQueue: nil)
let url: NSURL = NSURL(string: "https://www.myurl.com")!
let urlRequest = NSMutableURLRequest(url: url as URL)

urlRequest.httpMethod = "POST"
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

let uploadTask = backgroundSession.uploadTask(with: urlRequest as URLRequest, fromFile: path)

uploadTask.resume()

の初期化にクロージャを追加しようとしましたuploadTaskが、xcode はそれが不可能であるというエラーを表示しました。

カスタム クラス CustomDelegate があります。

class CustomDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate {

static var sharedInstance = CustomDelegate()

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    print("\(session.configuration.identifier!) received data: \(data)")
    do {
        let parsedData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]
        let status = parsedData["status"] as! NSDictionary
        let statusCode = status["httpCode"] as! Int

        switch statusCode {
        case 200:
            // Do something
        case 400:
            // Do something
        case 401:
            // Do something
        case 403:
            // Do something
        default:
            // Do something
        }
    }
    catch {
        print("Error parsing response")
    }
}
}

また、デリゲートの他の関数も実装します。

私が欲しいのは、アップロードが完了したことをどうにかして知って、UI とデータベースを更新できるようにすることですCustomDelegate

4

1 に答える 1

4

リクエストの完了を検出することのみに関心がある場合、最も簡単な方法はクロージャーを使用することです。

class CustomDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate {

    static var sharedInstance = CustomDelegate()
    var uploadDidFinish: ((URLSessionTask, Error?) -> Void)?

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        DispatchQueue.main.async {
            uploadDidFinish?(task, error)
        }
    }

}

次に、ビューコントローラーは、リクエストを開始する前にこのクロージャーを設定します。

CustomDelegate.sharedInstance.uploadDidFinish = { [weak self] task, error in
    // update the UI for the completion here
}

// start the request here

複数の状況で UI を更新する場合 (たとえば、アップロードの完了時だけでなく、アップロードの送信時に進行する場合など)、理論的には複数のクロージャー (完了用と進行用) を設定できますが、多くの場合、独自のクロージャーを採用します。独自のデリゲート プロトコル パターン。(個人的には、誰が何のデリゲートなのか混乱を避けるために名前を変更CustomDelegateしますが、それはあなた次第です。)UploadManager

たとえば、次のようにします。

protocol UploadDelegate: class {
    func didComplete(session: URLSession, task: URLSessionTask, error: Error?)
    func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
}

次に、ネットワーク リクエスト マネージャー (CustomDelegate実装) で、delegateプロパティを定義します。

weak var delegate: UploadDelegate?

適切なURLSessionデリゲート メソッドで、カスタム デリゲート メソッドを呼び出して、ビュー コントローラーに情報を渡します。

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    // do whatever you want here

    DispatchQueue.main.async {
        delegate?.didComplete(session: session, task: task, didCompleteWithError: error)
    }
}

func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
    // do whatever you want here

    DispatchQueue.main.async {
        delegate?.didSendBodyData(session: session, task: task, bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
    }
}

次に、新しいプロトコルに準拠するようにビュー コントローラーを宣言し、これらのメソッドを実装します。

class ViewController: UIViewController, UploadDelegate {
    ...
    func startRequests() {
        CustomDelegate.sharedInstance.delegate = self

        // initiate request(s)
    }

    func didComplete(session: URLSession, task: URLSessionTask, error: Error?) {
        // update UI here
    }

    func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { 
        // update UI here
    }
}

ここで、このプロトコルを更新UploadDelegateしてモデル情報を取得し、それをパラメーターとしてメソッドに渡すこともできますが、これで基本的な考え方が示されることを願っています。


いくつかの小さな観察:

  1. セッションを作成するときは、おそらくコードからNSURLと型を削除する必要があります。たとえば、次のようになります。NSMutableURLRequest

    let url = URL(string: "https://www.myurl.com")!
    var urlRequest = URLRequest(url: url)
    
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
    let uploadTask = backgroundSession.uploadTask(with: urlRequest, fromFile: path)
    
    uploadTask.resume()
    
  2. statusCodeでを探していdidReceiveDataます。あなたは本当にそれをしているはずですdidReceiveResponse。また、通常、ステータス コードは から取得しますURLResponse

  3. で応答を解析していdidReceiveDataます。通常、これを行う必要があります(応答全体を受信するためにdidCompleteWithError複数の呼び出しが必要な場合に備えて)。didReceiveData

  4. これが何であるかはわかりませんmyObject.idが、あなたが選択した識別子"com.example.myObject\(myObject.id)"はやや疑わしいです:

    • URLSessionオブジェクトごとに新しいインスタンスを作成していますか? おそらく、すべての要求に対して 1 つが必要です。

    • アップロードがバックグラウンドで続行されている間にアプリが一時停止/投棄された場合、アプリが再起動されたときに、同じセッション オブジェクトを再インスタンス化する信頼できる方法はありますか?
       

    通常、すべてのアップロードに対して単一のアップロード セッションが必要であり、名前は一貫している必要があります。今までのようにできないと言っているわけではありませんが、追加の作業を行わずにこれらのセッションを再作成するのは問題があるようです。それはあなた次第です。

    これはすべて、アプリが終了し、アップロードが終了したときにバックグラウンドで再起動された場合に、バックグラウンド アップロード プロセスが機能することを確認することを意味します。これは不完全/壊れやすいように感じますが、うまくいけば、私はいくつかの誤った結論にジャンプしているだけであり、これはすべて機能しておりhandleEventsForBackgroundURLSession、簡潔にするために一部の詳細 (アプリのデリゲートなど) を共有していません (これはとても有難い)。

于 2016-11-18T22:00:16.800 に答える