Swift 为backgroundSession.uploadTask实现完成处理程序

Swift 为backgroundSession.uploadTask实现完成处理程序,swift,nsurlsessiondatatask,nsurlsessionuploadtask,Swift,Nsurlsessiondatatask,Nsurlsessionuploadtask,我已经(几乎)成功地实现了URLSessionDelegate、URLSessionTaskDelegate和URLSessionDataDelegate,这样我就可以在后台上传我的对象了。但我不确定如何实现完成处理程序,以便在服务器返回statuscode=200 我现在像这样开始uploadTask let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.myObject\(myO

我已经(几乎)成功地实现了
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

中更新我觉得很难(可能不可能?)的UI和数据库。如果您只对检测请求的完成感兴趣,最简单的方法是使用闭包:

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,而且在上传发送时更新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
delegate方法中,可以调用自定义委托方法将信息传递给视图控制器:

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()
    
  • 您正在
    direceivedata
    中查找
    statusCode
    。您真的应该在
    didReceiveResponse
    中这样做。另外,您通常从
    urresponse
    获取状态代码

  • 您正在分析
    didReceiveData
    中的响应。通常,您应该在
    didcompletewitheror
    中执行此操作(以防需要多次调用
    didcreceivedata
    才能接收整个响应)

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

    • 是否为每个对象创建新的
      URLSession
      实例?对于所有请求,您可能都需要一个

    • 当您的应用程序在后台继续上载时被挂起/丢弃,当应用程序重新启动时,您是否有可靠的方法重新启动相同的会话对象

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

    所有这些都是说,如果应用程序被终止,并且在上传完成后在后台重新启动,我会确保你测试你的后台上传过程是否有效。这感觉是不完整的/脆弱的,但希望我只是得出了一些不正确的结论,而你已经把这些都做好了,只是为了简洁起见没有分享一些细节(例如,你的应用程序代理的
    handleEventsForBackgroundURLSession


  • 实施。如果您希望进行进度更新,可以使用.Unrelated,但不必实例化
    NSURL
    NSMutableURLRequest
    并强制转换为
    URL
    URLRequest
    ,您应该首先创建
    URL
    URLRequest
    。唯一的诀窍是,既然您想变异
    URLRequest
    ,就使用
    var
    ,而不是
    let
    @Rob,我已经这样做了,但我的问题是如何“退出”
    CustomDelegate
    类。例如,如果我在
    mainViewController
    中,我调用了一个函数来设置
    uploadTask
    ,那么我想更新用户界面。例如,您给自定义委托类一些机制来通知主视图控制器。典型的方法是“完成处理程序”闭包或协议委托模式(即:1.让您的自定义委托对象定义一个协议,通过该协议它将通知更新;2.拥有自己的该类型的
    委托
    属性;3.让主视图控制器符合该协议;4.启动请求时,将视图控制器设置为“自定义委托对象”的委托)。或发布通知。感谢@Rob的回答。是否可以在后台上传任务中使用完成处理程序关闭?谢谢,回答得很好!除了获取状态代码之外,我已经让它工作了。我认为我实现的
    didReceiverResponse
    不正确,因为无论发生什么情况,它都不会被调用。知道吗这是为什么?干杯。我会仔细检查方法签名并确保其正确性:。否则,我建议发布一个新问题,并添加一个。@Rob我正在尝试为具有后台会话的下载任务实现一个委托,但我的任务没有完成