为 backgroundSession.uploadTask 实施完成处理程序

Implementing completion handlers for backgroundSession.uploadTask

我(几乎)成功实施了 URLSessionDelegateURLSessionTaskDelegateURLSessionDataDelegate,这样我就可以在后台上传我的对象了。但是我不确定如何实现完成处理程序,以便在服务器 returns 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 显示不可能的错误。

我有我的自定义 class 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 和数据库。 20=]

如果您只对检测请求的完成感兴趣,最简单的方法是使用闭包:

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. 创建会话时,您可能应该从代码中删除 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 实例吗?您可能需要一个来满足所有请求。

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

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

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