为backgroundSession.uploadTask

时间:2016-11-17 14:18:32

标签: swift nsurlsessiondatatask nsurlsessionuploadtask

我(几乎)成功实施了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内更新我认为很难(可能不可能?)的用户界面和数据库。

1 个答案:

答案 0 :(得分:2)

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

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

如果你想在多种情况下更新你的用户界面(例如,不仅在上传完成时,而且在发送上传时进度),理论上你可以设置多个闭包(一个用于完成,一个用于进度),但通常是你' 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. 您正在statusCode中寻找didReceiveData。你真的应该在didReceiveResponse中这样做。此外,您通常会从URLResponse获取状态代码。

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

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

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

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

    通常,您需要为所有上传设置一个上传会话,并且名称应保持一致。我不是说你不能以你的方式做到这一点,但似乎在没有经过一些额外工作的情况下重新创建这些会话会有问题。这取决于你。

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