使用 NSURLSession 和 Queue 上传数据
Uploading Data Using NSURLSession and Queue
我正在设计一个聊天应用程序,我已经设置了以下机制供用户上传消息。基本上,我将消息推送到队列中并一个接一个地上传。当队列为空时,我每秒调用 finishedUploading
,其中 运行s,如果队列中有任何东西,我会重新 运行s 任务。
var uploadQueue:[UploadMessage]?
let session = NSURLSession.sharedSession()
let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)
// RETURNS AMOUNT OF ITEMS STILL IN QUEUE
func getRemainingActiveUploads() -> Int {
return (self.uploadQueue != nil) ? self.uploadQueue!.count : 0
}
//REMOVES MESSAGE FROM QUEUE ONCE UPLOADED
func removeMessageFromUploadQueue(messageToBeRemoved : UploadMessage) {
if (uploadQueue != nil) {
dispatch_sync(lockQueue) {
self.uploadQueue = self.uploadQueue?.filter({[=10=].date!.compare(messageToBeRemoved.date!) == NSComparisonResult.OrderedSame})
}
}
}
var uploadTimer : NSTimer?
// CALLED ONLY WHEN UPLOADQUEUE IS EMPTY, RERUNS THE UPLOAD FUNCTION AFTER 1 SECOND
func finishedUploading() {
uploadTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(uploadAllLinks), userInfo: nil, repeats: false)
if (needToRefetch) {
needToRefetch = false
newMessageReceived()
}
}
func uploadAllLinks()
{
uploadTimer?.invalidate()
uploadTimer = nil
// suspending queue so they don't all finish before we can show it
session.delegateQueue.suspended = true
session.delegateQueue.maxConcurrentOperationCount = 1
let myUrl = NSURL(string: "http://****")
// create tasks
if (uploadQueue != nil) {
if (uploadQueue?.count > 0) {
for message in uploadQueue!
{
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
request.timeoutInterval = 10
request.HTTPShouldHandleCookies=false
var postString = "sender=" + message.sender!
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let dltask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
if data != nil
{
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
dispatch_async(dispatch_get_main_queue(), {
if let errorToken = jsonArray["error"] as! Bool? {
if !errorToken {
self.uploadQueue = self.uploadQueue!.filter({[=10=].date!.compare(message.date!) != NSComparisonResult.OrderedSame})
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
})
}
catch {
print("Error: \(error)")
}
}
})
print("Queuing task \(dltask)")
dltask.resume()
}
session.delegateQueue.suspended = false
}
else {
finishedUploading()
}
// resuming queue so all tasks run
}
}
现在这在以下两种情况下工作正常:
- 队列为空 ->
finishedUploading
被调用并且 uploadAllLinks
每秒 运行 以检查 uploadQueue
中的项目
- 队列有一项 -> 一项被发布,
remaining == 0
因此 finishedUploading
被称为
但是,当队列中有多个项目时,第一个被上传,if remaining == 0
失败,然后什么也没有发生。我不明白为什么此时队列中的其他项目的 for 循环不是 运行。
我怀疑问题出在您的 10 秒超时间隔上。一旦数据任务 创建 就开始计时,如果它保持空闲(没有接收到新数据)超过十秒则终止任务。
如果您有多个任务并且 OS 一次只允许上传其中的一个或两个,那么任何排队等待开始的任务将永远不会完成。我认为文档没有提到这一点。
在实践中,这种设计使得 NSURLSession 的队列不太理想,因此,大多数人似乎都编写了自己的队列并自行处理并发限制,确保每个任务都在它应该开始之前创建运行。我建议做类似的事情:
- 创建一个方法来启动队列中的下一个上传,或者如果队列为空则调用 "everything complete" 方法——基本上是循环的主体。
- 调用该方法开始第一次上传,而不是循环本身。
- 在您的完成处理程序中(在该方法内),以半递归方式调用该方法以开始下一次上传。
此外,10 秒对于超时间隔来说太短了,除非您的设备安装在墙上并且使用 Wi-Fi 且信号稳定。不稳定的 Wi-Fi 和微弱的蜂窝信号会导致严重的延迟,因此 IIRC,默认值为 120 秒,尽管我在各个地方都读过 60 秒。无论哪种方式,您都不想使用 10 秒。如此短的超时时间几乎可以保证您的应用程序绝对不可靠。
我正在设计一个聊天应用程序,我已经设置了以下机制供用户上传消息。基本上,我将消息推送到队列中并一个接一个地上传。当队列为空时,我每秒调用 finishedUploading
,其中 运行s,如果队列中有任何东西,我会重新 运行s 任务。
var uploadQueue:[UploadMessage]?
let session = NSURLSession.sharedSession()
let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)
// RETURNS AMOUNT OF ITEMS STILL IN QUEUE
func getRemainingActiveUploads() -> Int {
return (self.uploadQueue != nil) ? self.uploadQueue!.count : 0
}
//REMOVES MESSAGE FROM QUEUE ONCE UPLOADED
func removeMessageFromUploadQueue(messageToBeRemoved : UploadMessage) {
if (uploadQueue != nil) {
dispatch_sync(lockQueue) {
self.uploadQueue = self.uploadQueue?.filter({[=10=].date!.compare(messageToBeRemoved.date!) == NSComparisonResult.OrderedSame})
}
}
}
var uploadTimer : NSTimer?
// CALLED ONLY WHEN UPLOADQUEUE IS EMPTY, RERUNS THE UPLOAD FUNCTION AFTER 1 SECOND
func finishedUploading() {
uploadTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(uploadAllLinks), userInfo: nil, repeats: false)
if (needToRefetch) {
needToRefetch = false
newMessageReceived()
}
}
func uploadAllLinks()
{
uploadTimer?.invalidate()
uploadTimer = nil
// suspending queue so they don't all finish before we can show it
session.delegateQueue.suspended = true
session.delegateQueue.maxConcurrentOperationCount = 1
let myUrl = NSURL(string: "http://****")
// create tasks
if (uploadQueue != nil) {
if (uploadQueue?.count > 0) {
for message in uploadQueue!
{
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
request.timeoutInterval = 10
request.HTTPShouldHandleCookies=false
var postString = "sender=" + message.sender!
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let dltask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
if data != nil
{
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
dispatch_async(dispatch_get_main_queue(), {
if let errorToken = jsonArray["error"] as! Bool? {
if !errorToken {
self.uploadQueue = self.uploadQueue!.filter({[=10=].date!.compare(message.date!) != NSComparisonResult.OrderedSame})
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
})
}
catch {
print("Error: \(error)")
}
}
})
print("Queuing task \(dltask)")
dltask.resume()
}
session.delegateQueue.suspended = false
}
else {
finishedUploading()
}
// resuming queue so all tasks run
}
}
现在这在以下两种情况下工作正常:
- 队列为空 ->
finishedUploading
被调用并且uploadAllLinks
每秒 运行 以检查uploadQueue
中的项目
- 队列有一项 -> 一项被发布,
remaining == 0
因此finishedUploading
被称为
但是,当队列中有多个项目时,第一个被上传,if remaining == 0
失败,然后什么也没有发生。我不明白为什么此时队列中的其他项目的 for 循环不是 运行。
我怀疑问题出在您的 10 秒超时间隔上。一旦数据任务 创建 就开始计时,如果它保持空闲(没有接收到新数据)超过十秒则终止任务。
如果您有多个任务并且 OS 一次只允许上传其中的一个或两个,那么任何排队等待开始的任务将永远不会完成。我认为文档没有提到这一点。
在实践中,这种设计使得 NSURLSession 的队列不太理想,因此,大多数人似乎都编写了自己的队列并自行处理并发限制,确保每个任务都在它应该开始之前创建运行。我建议做类似的事情:
- 创建一个方法来启动队列中的下一个上传,或者如果队列为空则调用 "everything complete" 方法——基本上是循环的主体。
- 调用该方法开始第一次上传,而不是循环本身。
- 在您的完成处理程序中(在该方法内),以半递归方式调用该方法以开始下一次上传。
此外,10 秒对于超时间隔来说太短了,除非您的设备安装在墙上并且使用 Wi-Fi 且信号稳定。不稳定的 Wi-Fi 和微弱的蜂窝信号会导致严重的延迟,因此 IIRC,默认值为 120 秒,尽管我在各个地方都读过 60 秒。无论哪种方式,您都不想使用 10 秒。如此短的超时时间几乎可以保证您的应用程序绝对不可靠。