AFNetworking 3.0 在应用移至后台时继续上传
AFNetworking 3.0 continuing upload when app moves to background
我的应用在前台开始向服务器上传内容。由于此过程可能需要一些时间,因此用户可以很好地将应用程序转移到后台。
我正在使用 AFHTTPSessionManager 提交上传:
let sessionManager = AFHTTPSessionManager()
sessionManager.requestSerializer.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-type")
sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
}, progress: { (progress) -> Void in
}, success: { (task, responseObject) -> Void in
print(responseObject)
success(responseObject: responseObject)
}, failure: { (task, error) -> Void in
print(error)
if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
do {
let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers)
failure(error: error, responseObject: json)
} catch let error {
print(error)
}
}
})
我需要此过程在应用程序处于后台状态时继续,直到完成。有一个很好的 SO 答案 here 让我走上了正确的轨道,但在某些地方我仍然一头雾水。我尝试使用链接答案中的 "BackgroundSessionManager" class 将我的上传调用更改为:
BackgroundSessionManager.sharedManager().POST(NetworkManager.kBaseURLString + "fulfill_request", parameters: params, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
}, progress: nil, success: nil, failure: nil)?.resume()
并将其添加到我的 AppDelegate 中:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
BackgroundSessionManager.sharedManager().savedCompletionHandler = completionHandler
}
但是我在一些后台线程上遇到 EXC_BAD_ACCESS 崩溃,几乎没有信息。我什至正确接近这个?
一些观察:
您正在构建一个多部分请求,但随后将 Content-Type
header 强制为 application/x-www-form-urlencoded
。但这不是多部分请求的有效内容 header。我建议删除请求 header 的手动配置(或告诉我们您这样做的原因)。 AFNetworking 将为您设置Content-Type
。
您可能根本不想理会背景 session,您可能只想请求一点时间来完成请求,即使在用户离开应用后也是如此。
var backgroundTask = UIBackgroundTaskInvalid
func uploadImageWithData(dataObject: NSData, mimeType: String, urlStr: String, params: [String: String]?, success: (responseObject: AnyObject?) -> (), failure: (error: NSError, responseObject: AnyObject) -> ()) {
let app = UIApplication.sharedApplication()
let endBackgroundTask = {
if self.backgroundTask != UIBackgroundTaskInvalid {
app.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
}
backgroundTask = app.beginBackgroundTaskWithName("com.domain.app.imageupload") {
// if you need to do any addition cleanup because request didn't finish in time, do that here
// then end the background task (so iOS doesn't summarily terminate your app
endBackgroundTask()
}
let sessionManager = AFHTTPSessionManager()
sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
}, progress: { (progress) -> Void in
}, success: { (task, responseObject) -> Void in
print(responseObject)
success(responseObject: responseObject)
endBackgroundTask()
}, failure: { (task, error) -> Void in
print(error)
if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
do {
let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers)
failure(error: error, responseObject: json)
} catch let error {
print(error)
}
}
endBackgroundTask()
})
}
我建议添加一个 exception breakpoint,看看这是否有助于您找到违规行。
坦率地说,如果异常在 NSURLSession
内部处理中异步发生,这可能无济于事,但这是我在尝试追踪异常源时首先尝试的事情。
如果您真的觉得必须使用后台 session(即您的上传任务可能需要超过 beginBackgroundTaskWithName
允许的三分钟),然后注意后台任务必须是上传或下载任务。
但是,我相信 POST
方法会创建一个数据任务。在 iOS 7 中,您根本不允许使用后台 session 的数据任务。在iOS8中,可以用后台任务发起数据任务,但是必须用NSURLSessionResponseBecomeDownload
响应didReceiveResponse
,例如在 BackgroundSessionManager
的 configureDownloadFinished
中,您可以尝试添加:
[self setDataTaskDidReceiveResponseBlock:^NSURLSessionResponseDisposition(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response) {
return NSURLSessionResponseBecomeDownload;
}];
或者,如果您再次必须使用后台 session,那么您可能需要手动构建 NSURLRequest
object 并将其作为上传提交或下载任务而不是使用 POST
。 AFNetworking 提供了将请求的构建和请求的提交分离的机制,因此如果您需要走那条路,请告诉我们,我们可以告诉您如何去做。
底线,beginBackgroundTaskWithName
比使用后台 NSURLSession
更容易请求一点时间来完成请求。仅在绝对必要时才执行后者(例如,您很可能需要三分钟以上才能完成请求)。
我的应用在前台开始向服务器上传内容。由于此过程可能需要一些时间,因此用户可以很好地将应用程序转移到后台。
我正在使用 AFHTTPSessionManager 提交上传:
let sessionManager = AFHTTPSessionManager()
sessionManager.requestSerializer.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-type")
sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
}, progress: { (progress) -> Void in
}, success: { (task, responseObject) -> Void in
print(responseObject)
success(responseObject: responseObject)
}, failure: { (task, error) -> Void in
print(error)
if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
do {
let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers)
failure(error: error, responseObject: json)
} catch let error {
print(error)
}
}
})
我需要此过程在应用程序处于后台状态时继续,直到完成。有一个很好的 SO 答案 here 让我走上了正确的轨道,但在某些地方我仍然一头雾水。我尝试使用链接答案中的 "BackgroundSessionManager" class 将我的上传调用更改为:
BackgroundSessionManager.sharedManager().POST(NetworkManager.kBaseURLString + "fulfill_request", parameters: params, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
}, progress: nil, success: nil, failure: nil)?.resume()
并将其添加到我的 AppDelegate 中:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
BackgroundSessionManager.sharedManager().savedCompletionHandler = completionHandler
}
但是我在一些后台线程上遇到 EXC_BAD_ACCESS 崩溃,几乎没有信息。我什至正确接近这个?
一些观察:
您正在构建一个多部分请求,但随后将
Content-Type
header 强制为application/x-www-form-urlencoded
。但这不是多部分请求的有效内容 header。我建议删除请求 header 的手动配置(或告诉我们您这样做的原因)。 AFNetworking 将为您设置Content-Type
。您可能根本不想理会背景 session,您可能只想请求一点时间来完成请求,即使在用户离开应用后也是如此。
var backgroundTask = UIBackgroundTaskInvalid func uploadImageWithData(dataObject: NSData, mimeType: String, urlStr: String, params: [String: String]?, success: (responseObject: AnyObject?) -> (), failure: (error: NSError, responseObject: AnyObject) -> ()) { let app = UIApplication.sharedApplication() let endBackgroundTask = { if self.backgroundTask != UIBackgroundTaskInvalid { app.endBackgroundTask(self.backgroundTask) self.backgroundTask = UIBackgroundTaskInvalid } } backgroundTask = app.beginBackgroundTaskWithName("com.domain.app.imageupload") { // if you need to do any addition cleanup because request didn't finish in time, do that here // then end the background task (so iOS doesn't summarily terminate your app endBackgroundTask() } let sessionManager = AFHTTPSessionManager() sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock: { (formData) -> Void in formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType) }, progress: { (progress) -> Void in }, success: { (task, responseObject) -> Void in print(responseObject) success(responseObject: responseObject) endBackgroundTask() }, failure: { (task, error) -> Void in print(error) if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData { do { let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers) failure(error: error, responseObject: json) } catch let error { print(error) } } endBackgroundTask() }) }
我建议添加一个 exception breakpoint,看看这是否有助于您找到违规行。
坦率地说,如果异常在
NSURLSession
内部处理中异步发生,这可能无济于事,但这是我在尝试追踪异常源时首先尝试的事情。如果您真的觉得必须使用后台 session(即您的上传任务可能需要超过
beginBackgroundTaskWithName
允许的三分钟),然后注意后台任务必须是上传或下载任务。但是,我相信
POST
方法会创建一个数据任务。在 iOS 7 中,您根本不允许使用后台 session 的数据任务。在iOS8中,可以用后台任务发起数据任务,但是必须用NSURLSessionResponseBecomeDownload
响应didReceiveResponse
,例如在BackgroundSessionManager
的configureDownloadFinished
中,您可以尝试添加:[self setDataTaskDidReceiveResponseBlock:^NSURLSessionResponseDisposition(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response) { return NSURLSessionResponseBecomeDownload; }];
或者,如果您再次必须使用后台 session,那么您可能需要手动构建
NSURLRequest
object 并将其作为上传提交或下载任务而不是使用POST
。 AFNetworking 提供了将请求的构建和请求的提交分离的机制,因此如果您需要走那条路,请告诉我们,我们可以告诉您如何去做。
底线,beginBackgroundTaskWithName
比使用后台 NSURLSession
更容易请求一点时间来完成请求。仅在绝对必要时才执行后者(例如,您很可能需要三分钟以上才能完成请求)。