【问题标题】:Implementing completion handlers for backgroundSession.uploadTask为 backgroundSession.uploadTask 实现完成处理程序
【发布时间】:2017-04-01 03:14:04
【问题描述】:

我已经(几乎)成功实现了URLSessionDelegateURLSessionTaskDelegateURLSessionDataDelegate,这样我就可以在后台上传我的对象了。但是我不确定如何实现完成处理程序,以便在服务器返回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 中更新我觉得很难(也许不可能?)的 UI 和数据库。

【问题讨论】:

  • 实现didCompleteWithError。如果您想随时更新进度,可以使用didSendBodyData
  • 不相关,但不是实例化NSURLNSMutableURLRequest 并转换为URLURLRequest,您应该首先创建URLURLRequest。唯一的技巧是,既然你想改变URLRequest,请使用var,而不是let
  • @Rob 我已经做到了,但我的问题是如何“摆脱”CustomDelegate 类。例如,如果我在 mainViewController 中并且我调用了一个设置 uploadTask 的函数,那么我想更新 UI 例如
  • 你给你的自定义委托类一些机制来通知主视图控制器。典型的方法是“完成处理程序”闭包或协议委托模式(即 1. 让您的自定义委托对象定义一个协议,它将通过该协议通知更新,2. 拥有自己的该类型的 delegate 属性,3.让主视图控制器符合该协议,并且 4. 当您启动请求时,将视图控制器设置为“自定义委托对象”的委托)。或发布通知。
  • 感谢@Rob 的回答。是否可以在后台上传任务中使用完成处理程序闭包?

标签: swift nsurlsessiondatatask nsurlsessionuploadtask


【解决方案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

如果您想在多种情况下更新您的 UI(例如,不仅是上传完成,还有上传发送的进度),理论上您可以设置多个关闭(一个用于完成,一个用于进度),但通常您' d 采用您自己的委托协议模式。 (就个人而言,我会将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. 创建会话时,您可能应该从代码中删除 NSURLNSMutableURLRequest 类型,例如:

    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. 您正在didReceiveData 中寻找statusCode。你真的应该在didReceiveResponse 中这样做。此外,您通常会从URLResponse 获得状态代码。

  3. 您正在解析didReceiveData 中的响应。通常,您应该在 didCompleteWithError 中执行此操作(以防需要多次调用 didReceiveData 才能收到整个响应)。

  4. 我不知道这个myObject.id 是什么,但是您选择的标识符"com.example.myObject\(myObject.id)" 有点可疑:

    • 您是否正在为每个对象创建一个新的URLSession 实例?您可能需要一个来处理所有请求。

    • 当您的应用程序在后台继续上传时暂停/放弃时,当应用程序重新启动时,您是否有可靠的方法来重新实例化相同的会话对象?
       

    通常,您希望所有上传的上传会话都在一个上传会话中,并且名称应该一致。我并不是说你不能按照你的方式去做,但在不进行一些额外工作的情况下重新创建这些会话似乎会有问题。这取决于你。

    所有这一切都是说,如果应用程序终止并在上传完成后在后台重新启动,我会确保您测试您的后台上传过程是否有效。感觉这是不完整/脆弱的,但希望我只是跳到一些不正确的结论,你已经得到了这一切工作,只是为了简洁起见没有分享一些细节(例如你的应用程序代表的handleEventsForBackgroundURLSession) (非常感谢)。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多