Реализация обработчиков завершения для backgroundSession.uploadTask

Я (почти) успешно реализовал 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")
    }
}
}

Он также реализует другие функции для делегатов.

Я хочу как-то знать, что загрузка выполнена, чтобы я мог обновить пользовательский интерфейс и базу данных, которые, как мне кажется, трудно (возможно, невозможно?) Изнутри CustomDelegate,

1 ответ

Решение

Если вы заинтересованы только в обнаружении завершения запроса, самый простой подход - использовать замыкание:

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

Если вы хотите обновить свой пользовательский интерфейс для нескольких ситуаций (например, не только по окончании загрузки, но и по мере отправки загрузки), вы теоретически можете установить несколько замыканий (одно для завершения, другое для процесса), но часто вы принимаете собственный шаблон протокола делегата. (Лично я бы переименовал 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 экземпляр для каждого объекта? Вы, вероятно, хотите один для всех запросов.

    • Когда ваше приложение приостанавливается / отбрасывается, пока загрузка продолжается в фоновом режиме, когда приложение перезапускается, есть ли у вас надежный способ восстановления тех же объектов сеанса?

    Как правило, вы хотите один сеанс загрузки для всех ваших загрузок, и имя должно быть согласованным. Я не говорю, что вы не можете сделать это так, как вы, но кажется, что воссоздание этих сессий будет проблематичным без какой-либо дополнительной работы. Тебе решать.

    Все это означает, что я проверю, работает ли ваш фоновый процесс загрузки, если приложение завершается и перезапускается в фоновом режиме после завершения загрузки. Такое ощущение, что это неполно / хрупко, но, надеюсь, я просто делаю некоторые неверные выводы, и у вас все это работает, и вы просто не поделились некоторыми деталями (например, делегат вашего приложения handleEventsForBackgroundURLSession) для краткости (что высоко ценится).

Другие вопросы по тегам