Реализация обработчиков завершения для 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
протокол для сбора информации о модели и передачи ее в качестве параметра вашим методам, но, надеюсь, это иллюстрирует основную идею.
Некоторые незначительные замечания:
При создании сеанса вы, вероятно, должны
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()
Ты ищешь
statusCode
вdidReceiveData
, Вы действительно должны делать это вdidReceiveResponse
, Кроме того, вы обычно получаете код состояния отURLResponse
,Вы анализируете ответ в
didReceiveData
, Как правило, вы должны сделать это вdidCompleteWithError
(на случай, если потребуется несколько звонковdidReceiveData
получить полный ответ).Я не знаю что это
myObject.id
есть, но идентификатор, который вы выбрали,"com.example.myObject\(myObject.id)"
Сомнительно:Вы создаете новый
URLSession
экземпляр для каждого объекта? Вы, вероятно, хотите один для всех запросов.Когда ваше приложение приостанавливается / отбрасывается, пока загрузка продолжается в фоновом режиме, когда приложение перезапускается, есть ли у вас надежный способ восстановления тех же объектов сеанса?
Как правило, вы хотите один сеанс загрузки для всех ваших загрузок, и имя должно быть согласованным. Я не говорю, что вы не можете сделать это так, как вы, но кажется, что воссоздание этих сессий будет проблематичным без какой-либо дополнительной работы. Тебе решать.
Все это означает, что я проверю, работает ли ваш фоновый процесс загрузки, если приложение завершается и перезапускается в фоновом режиме после завершения загрузки. Такое ощущение, что это неполно / хрупко, но, надеюсь, я просто делаю некоторые неверные выводы, и у вас все это работает, и вы просто не поделились некоторыми деталями (например, делегат вашего приложения
handleEventsForBackgroundURLSession
) для краткости (что высоко ценится).