I've got NSURLConnection with timeout = 30s which is uploading an image on server.
If connection is horrible and call delegate method didFailWithError: then i need to cancel current connection.
But if i just call the [myConnection cancel] connection will still alive but will not call delegates methods (apple docs say it - NSURLConnection cancel method). And i want to abort connection but not only remove delegate methods. How i can do what?
upd:
My problem is if connection is fails by timeout - in business logic i must recreate connection with similar request. If i have got horrible connection for 1 min and after that connection will be good - server will get a lot of (about 3 times retry count) photos. But first 2 connections is canceled. –
At the moment i make "dirty hack" like "if it's photo uploading request" - do not retry recreate connection.
I do a ton of network stuff and don't recall a scenario where everything was successfully received but the iOS app timed out. I'm trying to grok the scenario you describe, where you're seeing this happen a lot and I'm not seeing how that would happen. We might need to see some of your code.
Regardless, when you cancel a NSURLConnection, it not only stops the delegate methods from being called, but it stops the upload, too. I just did a test:
I attempting to upload a 20mb file (non-chunked request);
At the 1mb mark (as identified by didSendBodyData), I canceled the connection (by calling [connection cancel]);
I immediately stopped receiving any delegate messages at that point;
Looking at Charles, I'm only seeing 1.3mb of data in the hex log of the request. When I look at the "Network" tab of the Mac OS "Activity Monitor" and looking at by "Sent Bytes", it's at 2.1mb uploaded.
So canceling a connection will stop further data from being sent. Perhaps if there is some transmission in progress that still gets out (that's the asynchronous world we live it), but the it's not true to conclude that canceled connections will routinely send their full HTTP request. There must be something about the nature of the timeout that is unique to your environment.
In terms of your immediate problem, I might suggest that when uploading a file that the iOS app assign some unique identifier to the upload so that the server code can immediately recognize duplicate requests and handle them appropriately. But the question is why you are seeing so many time-outs and notably ones where the request appears to be successfully received in toto, but the response is not. That's very curious.
You cannot forcefully abort an ongoing connection.
In case if connection is not yet started cancel and unscheduleFromRunLoop for the NSURLConnection will work.
Try with following step
[myConnection cancel];
myConnection = nil;
Might be helpful in your case and If this step is not working then also try with myConnection.delegate = nil;
Related
I am doing a get API request and everything works fine, but I am getting the following warning in the console.
Task <13369ECB-128E-41B7-B9E4-DC7D3E47D0C1>.<2> finished with error -
code: -999
This only occurs for a certain API endpoint. This makes no sense to me at all. I thought -999 stands for cancelled request, but my requests are finished.
I think this might be a security issue simply because all my get requests work for multiple api endpoints, but not a specific one. Any suggestions are appreciated.
Yes, this means it was canceled, but the question is why
be patient to make sure you didn't cancel the request.
returned when an asynchronous load is canceled. A Web Kit framework delegate will receive this error when it performs a cancel operation on a loading resource.
may be caused by an invalid SSL certificate
I need something similar to Facebook's offline post capabilities. Basically I want users to create content locally on the device regardless of connection state, and whenever internet becomes available it should POST/PUT to the server.
I've searched the internet for a solution and I found that NSURLSessionUploadTask can be used for POST-ing in the background. But I couldn't figure out if the following scenarios are supported:
Will my task remain in the background queue when the user is offline and will the operating system try to execute items in the queue upon reconnecting with a network?
What happens if the application is force-closed by the user or crashes?
What happens if the operation fails?
First of all, background NSURLSession allows file upload only. If that is ok for you:
The task will be in the queue until it receives a server answer.
If your app is force-closed, the task will still be executing. When the request is done, your app will be launched in non-interactive background state and receive application:handleEventsForBackgroundURLSession:completionHandler:. After you process the signal and call the completion handler or 30 second timeout, the app will be closed.
I the operation fails, you will receive URLSession:task:didCompleteWithError:
There is a good tutorial on background NSURLSessions. I suggest you to read all 4 parts of this great article.
If file upload is not an option for you, i suggest you to save information into local database and then wait for internet is reachable. (a good approach here is use of Reachability library, Alamofire allows to do that too). When internet becomes available, simply call your http requests with saved data.
We were running into connectivity issues with our internal apps, so we wrote a Swift framework that allows any network operations to be enqueued and sent whenever the device has access to the internet -
https://cocoapods.org/pods/OfflineRequestManager. You'll still have to handle the network request itself within the object conforming to OfflineRequest, but it sounds like a good fit for your use case.
The simplest use case would look something like the following, though most actual cases (saving to disk, specific request data, etc.) will have a few more hoops to jump through:
import OfflineRequestManager
class SimpleRequest: OfflineRequest {
func perform(completion: #escaping (Error?) -> Void) {
doMyNetworkRequest(withCompletion: { response, error in
handleResponse(response)
completion(error)
})
}
}
///////
OfflineRequestManager.defaultManager(queueRequest: SimpleRequest())
I am using NSURLSession in background mode, to upload images from file using a NSURLSessionUploadTask, and the delegate callbacks. All this works perfectly fine if the app remains on the background as opposed to getting killed by the user.
However if while there are still pending uploads, and the app is killed using the home screen, once the app is restarted and the images that did not upload get requeued as upload tasks with a new NSURLSession with the same background identifier, something really odd happens:
1) The tasks are created only once, and I assign a task description to them when that happens, also NSURLSession assigns them a task identifier which is unique per session. My NSURLSession background is a singleton.
2) The tasks fail almost immediately with this error:
Error Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)" UserInfo=0x174669880 {NSErrorFailingURLStringKey=myURL, NSURLErrorBackgroundTaskCancelledReasonKey=0
and it calls the completion delegate method:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
passing the aforementioned error above. At this point I print out the task identifier and the task description which matches the task I created.
3) After all this tasks fail almost immediately, that is when things start to get weird, all those tasks get somehow put again into the NSURLSession without my code doing anything, note that using Charles I can see a single request going out, which in reality never fails. This second time they succeed and they call the didCompleteWithError again this time there is no error. At this point printing the task description and task identifier, surprisingly returns the same task description I assigned, BUT a different task identifier!. Meaning iOS is somehow recreating that task with the same task identifier and queuing it up again, even though it claims it failed. Note that the network request can be seen in Charles only once and it never really fails
This is problem because I cannot distinguish between real upload errors, and this pseudo errors generated by iOS, right now since I am not explicitly canceling any of those requests, I am checking the error code for canceled, and choosing to ignore it, since I know the task will just get magically requeued. This seems like a pretty obvious bug to me, and it can be reproduced all the time.
With the workaround in place everything works as expected, but this is a hack, and I can imagine I might at some point ignore a real canceling and never notify the client that the request in fact failed.
I have only found a couple of people online describing the issue, but no responses.
Is there a radar filed for this?. Is there any way to prevent this from happening?
I am using the 8.1 SDK.
I'm using NSURLConnection to send an HTTP request and running it with
[[NSURLConnection alloc] initWithRequest:self.request delegate:self];
where "self.request" is a configured NSMutableURLRequest object. Upon a network failure and callback to
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
I'd like to cancel the request and write the payload to file to be uploaded later. However if I reconnect too quickly the payload ends up getting sent still (and I later send the same payload from the file). In the failure callback, I've tried to use
[connection cancel];
But the http request still goes through upon reconnecting within a few seconds. Is this due to some retry mechanic that I can disable?
From Apple's docs on didFailWithError:
// Once the delegate receives this message, it will receive no further messages for connection.
So I'd say you have a bug somewhere else in the code. Once that delegate message is called, the connection is done and it will never be restarted unless you do so.
From the question it seems like you are trying to cancel the connection in the failure delegate method, is that correct? That should not be necessary since the connection is already dead. I'm also a bit confused when you say you "retry to quickly". I don't see any "retry" code so it would help if you could post that too.
Can you post more code to help diagnose the issue?
I have been using GKMatch for quite a while successfully in an app. I have been chasing down and issue with the game occasionally stopping and have tracked it down to packets being sent but not received. This happens only occasionally but I can't seem to track down why it happens.
All messages are sent using GKSendDataReliable.
Logging has shown that the packet is being sent from one device successfully, but it is never received at the target device.
//Code sample of sending method....
//self.model.match is a GKMatch instance
-(BOOL) sendDataToAllPlayers:(NSData *)data error:(NSError **)error {
[self.model.debugger addToLog:#"GKManager - sending data"];
return [self.model.match sendDataToAllPlayers:data withDataMode:GKSendDataReliable error:error];
}
...
//Code sample of receiving method....
// The match received data sent from the player.
-(void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
[self.model.debugger addToLog:#"GKManager - received data"];
[super didReceiveData:data fromPlayer:playerID];
}
What I see happen is that periodically (maybe 1 in 100 messages) is sent without error from the 'sendDataToAllPlayers' method, but the receiving device never hits the 'didReceiveData' method. My understanding is that using GKSendDataReliable should send messages and then won't send another until it receives an acknowledgement. Messages aren't received but new messages are sent from the same device.
The sending method returns 'YES' and error is nil, but the didReceiveData is never hit...!
Has anyone ever seen this? Does anyone have any ideas what this could be? I don't know what else I could do to debug this.
I confirm the bug.
I made an example project consistently reproducing the issue: https://github.com/rabovik/GKMatchPacketLostExample. I tested it on a weak internet connection (iPad 3 with Wi-Fi and iPhone 4S with EDGE; both on iOS 6.1.3), and some packets regularly get lost without any error from Game Center API. Moreover sometimes device stops receiving any data, while another still sends and receives messages successfully.
So if we are sure the bug exists, the only possible workaround is to add an extra transport layer for truly reliable delivery.
I wrote a simple lib for this purpose: https://github.com/rabovik/RoUTP. It saves all sent messages until acknowledgement for each received, resends lost and buffers received messages in case of broken sequence.
In my tests combination "RoUTP + GKMatchSendDataUnreliable" works even beter than "RoUTP + GKMatchSendDataReliable" (and of course better than pure GKMatchSendDataReliable which is not really reliable).
[Edit: RoUTP no longer seems to work properly in iOS9]
I did some testing yesterday at the edge of my wifi's range where packet loss was occuring. What happens is that when packets are lost using GKMatchSendDataReliable the player is abruptly disconnected from the GKMatch session. match:player:didChangeState is called with GKPlayerStateDisconnected and the player's ID is removed from the playerIDs dictionary. This happens with only slight packet loss. I can still browse the internet from this connection for instance.
Now, if I switch to sending packets unreliably, then match:player:didChangeState never fires and the match keeps going without a problem (except losing the occasional packet which might be important). It will only disconnect if the packet loss becomes substantial. Now this is where Yan's RoUTP library is handy, since we can keep track of and retry these important messages without having our players disconnected when they encounter slight packet loss.
Also, data sending using GKMatchSendDataReliable will only return YES if the message has been successfully queued for delivery. It does not tell you whether or not the message was successfully delivered. How could it? It returns right away.