为 backgroundSession.uploadTask 实施完成处理程序
Implementing completion handlers for backgroundSession.uploadTask
我(几乎)成功实施了 URLSessionDelegate
、URLSessionTaskDelegate
和 URLSessionDataDelegate
,这样我就可以在后台上传我的对象了。但是我不确定如何实现完成处理程序,以便在服务器 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
协议以捕获模型信息并将其作为参数传递给您的方法,但希望这能说明基本思想。
一些小观察:
创建会话时,您可能应该从代码中删除 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()
您正在 didReceiveData
中寻找 statusCode
。你真的应该在 didReceiveResponse
中这样做。此外,您通常会从 URLResponse
中获取状态代码。
您正在解析 didReceiveData
中的响应。通常,您应该在 didCompleteWithError
中执行此操作(以防需要多次调用 didReceiveData
才能收到整个响应)。
我不知道这个 myObject.id
是什么,但是您选择的标识符 "com.example.myObject\(myObject.id)"
有点可疑:
您要为每个对象创建一个新的 URLSession
实例吗?您可能需要一个来满足所有请求。
当您的应用程序处于 suspended/jettisoned 而上传在后台继续时,当应用程序重新启动时,您是否有可靠的方法来重新实例化相同的会话对象?
通常,您希望所有上传都在一个上传会话中进行,并且名称应该保持一致。我并不是说您 不能 按照您的方式进行操作,但似乎在不进行一些额外工作的情况下重新创建这些会话会有问题。由你决定。
所有这一切都是说,如果应用程序终止并在上传完成后在后台重新启动,我会确保您测试后台上传过程是否正常。这感觉就像是 incomplete/fragile,但希望我只是跳到一些不正确的结论,你已经完成了所有工作,只是没有分享一些细节(例如你的应用程序委托的 handleEventsForBackgroundURLSession
)为了简洁起见(非常感谢)。
我(几乎)成功实施了 URLSessionDelegate
、URLSessionTaskDelegate
和 URLSessionDataDelegate
,这样我就可以在后台上传我的对象了。但是我不确定如何实现完成处理程序,以便在服务器 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
协议以捕获模型信息并将其作为参数传递给您的方法,但希望这能说明基本思想。
一些小观察:
创建会话时,您可能应该从代码中删除
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()
您正在
didReceiveData
中寻找statusCode
。你真的应该在didReceiveResponse
中这样做。此外,您通常会从URLResponse
中获取状态代码。您正在解析
didReceiveData
中的响应。通常,您应该在didCompleteWithError
中执行此操作(以防需要多次调用didReceiveData
才能收到整个响应)。我不知道这个
myObject.id
是什么,但是您选择的标识符"com.example.myObject\(myObject.id)"
有点可疑:您要为每个对象创建一个新的
URLSession
实例吗?您可能需要一个来满足所有请求。当您的应用程序处于 suspended/jettisoned 而上传在后台继续时,当应用程序重新启动时,您是否有可靠的方法来重新实例化相同的会话对象?
通常,您希望所有上传都在一个上传会话中进行,并且名称应该保持一致。我并不是说您 不能 按照您的方式进行操作,但似乎在不进行一些额外工作的情况下重新创建这些会话会有问题。由你决定。
所有这一切都是说,如果应用程序终止并在上传完成后在后台重新启动,我会确保您测试后台上传过程是否正常。这感觉就像是 incomplete/fragile,但希望我只是跳到一些不正确的结论,你已经完成了所有工作,只是没有分享一些细节(例如你的应用程序委托的
handleEventsForBackgroundURLSession
)为了简洁起见(非常感谢)。