Reputation: 5567
I am designing a chat application and I have set up the following mechanism for users to upload messages. Basically, I push the messages onto a queue and upload them one after the other. When the queue is empty, I call finishedUploading
which runs every second and reruns the task if there is anything in the queue.
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({$0.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({$0.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
}
}
Now this works fine in the following two cases :
finishedUploading
gets called and uploadAllLinks
is run every second to check for items in uploadQueue
remaining == 0
hence finishedUploading
is called However, whenever the queue has more than one item, the first one gets uploaded, if remaining == 0
fails, and then nothing happens. I don't understand why the for loop is not run for the other items in the queue at this point.
Upvotes: 0
Views: 607
Reputation: 10417
I suspect that the problem is your 10-second timeout interval. That begins ticking as soon as the data task is created and terminates the task if it remains idle (without receiving new data) for more than ten seconds.
If you have multiple tasks and the OS is only allowed to upload one or two of them at a time, then any task that is queued up waiting to start will never complete. I don't think the documentation mentions that.
In practice, this design makes NSURLSession's queueing less than ideal, and as a result, most folks seem to write their own queues and handle the concurrency limiting on their own, ensuring that each task is created right before it should start running. I would suggest doing something similar:
Also, 10 seconds is way too short for the timeout interval unless your device is mounted to a wall and is on Wi-Fi with a guaranteed solid signal. Flaky Wi-Fi and weak cellular signals can result in serious latency, so IIRC, the default is 120 seconds, though I've read 60 in various places. Either way, you do not want to use 10 seconds. Such a short timeout would pretty much guarantee that your app will be hopelessly unreliable.
Upvotes: 1