Is it possible to handle a request timeout with a UIAlert? I would like to inform the user that there has been a time out. I set 0.0 just for testing to see if it would occur. The log does not print out so i do not believe i am handling correcting
request.timeoutInterval=0.0;
and to handle it:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if(error.code == NSURLErrorTimedOut){
NSLog(#"Time out");
}
}
I am using NSURLConnectionDelegate and NSURLConnectionDownloadDelegate
Thanks.
You can find a list of all the relevant error codes here. Note that -1009 is kCFURLErrorNotConnectedToInternet. To force a timeout, you need to have the ability to suppress the actual sending of the URL, or find one that just goes into a black hole.
Apple bundles Network Link Conditioner with Xcode (its in the networking tools I believe). There is a great article on this tool on NSHipster.
Another way (I believe) to get a timeout is to immediately after sending a request is to switch to another app, putting yours in the background. Wait 5 minutes, switch back, and look at the log. What you can do is in a demo app continually send out NSURLConnections - that is, once the first returns, send another. so there is always one outstanding. Now switch your app out - switch to another app in the simulator - wait, then return. You should be able to catch the condition this way, you can see the affect of changing the timeoutinterval value.
There was a long thread about timeouts in the private Apple forums for iOS networking - you may be able to find it. In the end, the system enforces a reasonably long minimum (as I recall), and you may be able to make it longer but not shorter. This hidden behavior has for sure baffled many people. The responder to the question was Quinn "The Eskimo", one of Apple's most experienced networking engineers.
Related
There are a handful of posts discussing how Game Center's push notifications were fairly unreliable in the sandbox. However, the sandbox is obfuscated with iOS 9 so, I'm not sure why my Game Center push notifications are so unreliable.
When I reply to the active exchange, the sender is rarely notified.
[exchange replyWithLocalizableMessageKey:#"EXCHANGE_REPLY" arguments:#[] data:data completionHandler:^(NSError *error) {
if (error)
{
NSLog(#"");
}
}];
On the senders device, if I refresh the match data, I'll see a pending reply. If I process the reply, everything works.
The same goes for this method:
- (void)sendExchangeToParticipants:(NSArray<GKTurnBasedParticipant *> *)participants
data:(NSData *)data
localizableMessageKey:(NSString *)key
arguments:(NSArray<NSString *> *)arguments
timeout:(NSTimeInterval)timeout
completionHandler:(void(^__nullable)(GKTurnBasedExchange *exchange, NSError *error))completionHandler
At this point, I'm thinking my best option is to run my own push notification logic to trigger updating match data. That or I've read that sending reminders is more reliable though I believe there are throttling limits around that.
Update
I've tried using only devices and not the simulator. Same issue. Looks like it's a pretty well known problem though. It's even noted in this book on page 766.
Update
Sending reminders didn't help.
Update
Often when replying to an exchange, I'll get this error from GameKit.
The connection to service named com.apple.gamed was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.
Exchanges has until Oct 2020 never actually worked as needed, nor as specified, due to a bug in the Apple backend. Now however, an Apple engineer seem to suggest it has been fixed - asking me to verify that it works. Which I intend to do ASAP (I just need to update Xcode) using my public project: https://github.com/Gatada/TurnBasedGameFlow
FURTHER DETAIL
A turn based exchange relies on the turn holder being notified when the exchange is completed, so the turn holder can resolve it (submit it to Game Center). This notification however, was never pushed to the turn holder.
As a result of this bug, the games we made had to rely on the turn holder re-loading the game after the exchange completes, and our code had to gracefully handle the turn submission failing due to game data being out-of-sync (caused by the completed exchange).
I had a one-on-one Game Center session with Apple during WWDC 2020, where I reported this issue with hard evidence (after all, this bug had been around since 2010) which convinced the Apple engineer. It took them 3 months to get back to me, and another 3 months for me to get back to them - hehe, bringing us to now.
Perhaps I have been reading the wrong stuff, but one thing that all of the literatures that I have been reading seem to agree on is that: iOS does not allow background threads to run for longer than ten minutes. That seems to violate one of the greatest principles of app development: the internet should be invisible to your users. So here is a scenario.
A user is going through a tunnel or flying on an airplane, which causes no or unreliable network. At that instant, the user pulls out my email app, composes an email, and hits the send button.
Question: How do I the developer make sure that the email is sent when network becomes available? Of course I am using email as a general example, but in reality I am dealing with a very much simple http situation where my app needs to send a POST to my server.
Side Note: on android, I use Path’s priority job queue, which allows me to set it and forget it (i.e. as soon as there is network it sends my email).
another Side Note: I have been trying to use NSOperationQueue with AFNetworking, but does not do it.
What you want to achieve can be done using a background NSURLSession. While AFNetworking is based on NSURLSession I’m not quite sure if it can be used with a background session that runs while your app doesn’t. But you don’t really need this, NSURLSession is quite easy to use as is.
As a first step you need to create a session configuration for the background session:
let config = URLSessionConfiguration.background(withIdentifier: "de.5sw.test")
config.isDiscretionary = true
config.waitsForConnectivity = true
The isDiscretionary property allows the system to decide when to perform the data transfer. waitsForConnectivity (available since iOS 11) makes the system wait if there is no internet connection instead of failing immediately.
With that configuration object you can create your URL session. The important part is to specify a delegate as the closure-based callbacks get lost when the app is terminated.
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
To perform your upload you ask the session to create an upload task and then resume it. For the upload task you first create your URLRequest that specifies the URL and all needed headers. The actual data you want to upload needs to be written to a file. If you provide it as a Data or stream object it cannot be uploaded after your app terminates.
let task = session.uploadTask(with: request, fromFile: fileUrl)
task.resume()
To get notified of success or failure of your upload you need to implement the URLSessionDataDelegate method urlSession(_:task:didCompleteWithError:). If error is nil the transfer was successful.
The final piece that is missing is to handle the events that happened while your app was not running. To do this you implement the method application(_:handleEventsForBackgroundURLSession:completionHandler:) in your app delegate. When the system decides that you need to handles some events for background transfers it launches your app in the background and calls this method.
In there you need first store the completion handler and then recreate your URLSession with the same configuration you used before. This then calls it’s delegate for the events you need to handle as usual. Once it is done with the events it calls the delegate method urlSessionDidFinishEvents(forBackgroundURLSession:). From there you need to call the completion handler that was passed to your app delegate.
The session configuration provides some more options:
timeoutIntervalForResource: How long the system should try to perform your upload. Default is 7 days.
sessionSendsLaunchEvents: If false the app will not be launched to handle events. They will be handled when the user opens the app manually. Defaults is true.
Here is a small sample project that shows how everything fits together: https://github.com/5sw/BackgroundUploadDemo
Your app needs to store the data internally and then you either need something which will cause the app to run in the background (but you shouldn't necessarily add something specially if you don't already have a reason to be doing it) or to wait until the user next brings the app to the foreground - then you can check for a network connection and make the call.
Note that e-mail is very different to a POST, because you can pass an e-mail off to the system mail app to send for you but you can't do exactly the same thing with a POST.
Consider looking also at NSURLSessionUploadTask if you can use it.
In three words: you don't.
And that's actually a good thing. I certainly do not want to have to think and speculate about my last 20 apps, if they are still running in the background, using memory and battery and bandwidth. Furthermore, they would be killed if more memory is needed. How would the user be able to predict if it completed its task successfully? He can't, and need to open the app anyhow to check.
As for the email example, I'd go with showing the email as "pending" (i.e. not sent), until it transferred correctly. Make it obvious to the user that he has to come back later to fulfill the job.
While every developer thinks that his app has an extremely good reason for backgrounding, reality is, for the user in 99% it's just a pain. Can you say "task manager"? ;-)
I wrote a pod that does pretty much this - https://cocoapods.org/pods/OfflineRequestManager. You'd have to do some work listening to delegate callbacks if you want to monitor whether the request is in a pending or completed/failed state, but we've been using it to ensure that requests go out in poor or no connectivity scenarios.
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'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;
I'm creating an online-shop-style app where users can browse different products on their iPad and order these products. The ordering process consists of creating an xml-file with the user's data and the relevant products he would like to order. But sometimes there might be the case, that users don't have an internet connection right now and I would like to create some mechanism, which checks every x minutes for an active internet connection and then tries to deliver the order-xml. It should repeat this step until it gets connected to the web and then just stop it, when all offline carts have been sent.
I have already been searching the web but only found ways to do this on iOS 7 (with UIBackgroundModes - fetch). But I don't want to use iOS 7 because the app is already done and I'm not planning to redesign it for iOS 7 (it's an Enterprise App). As far as I know, the current Background Execution time on iOS 6 is limited to something like 15 minutes, is that correct?
Any ideas on how to solve that?
Thanks.
EDIT:
I have tried the following in - (void)applicationDidEnterBackground:(UIApplication *)application
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] startNetworkTimer];
}];
and here is what should happen next:
- (void) startNetworkTimer{
if ([CommonCode getAllOfflineCartsForClient:nil].count > 0){
NSTimer *pauseTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(offlineCartLoop:) userInfo:nil repeats:YES];
}
}
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
[sender invalidate];
}
}
startNetworkTimer gets called as it should, but then it doesn't call the offlineCartLoop function :-(
EDIT 2:
I think the timer-thing was the problem. I'm now calling the offlineCartLoop function like this:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] offlineCartLoop:nil];
}];
and changed the offlineCartLoop function to this:
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
}else{
[NSThread sleepForTimeInterval:10.0];
[self offlineCartLoop:nil];
}
}
Seems to work, but will this run forever? Is there anything else I need to take care of?
There is no solution to what you want - there is no such thing as being able to periodically check every N minutes in the background unless it is within the time window granted by beginBackgroundTaskWithExpirationHandler.
However that only permits 10 minutes of execution time for iOS6 and earlier, or approximately 3 minutes for iOS7.
You cannot cheat and try and use a background mode if your app does not need it, and even the background modes do not permit you to freely run whenever you want.
Even the new background modes in iOS 7 do not permit you to run on a scheduled basis.
Your best best actually is iOS7 even though you don't want to migrate to iOS7 - the background fetch being the relevant mode (even though you are pushing not fetching). With that background mode you will be able to have the opportunity to execute but not when you decide, only when the OS decides - and the frequency of that depends upon how the user uses your app.
With iOS6 your options are even less restricted.
See iOS: Keep an app running like a service
Basically there just is no such thing as continuous background execution, nor periodic background execution, nor the app deciding when it wants to run when in the background.
If the user does not have an internet connection at the time they use your app to place the order then you should be notifying them of that anyway (if you don't then your app risks rejection from the app store) and maybe tell them to try again later.
If they are in flight mode the user will know they are in flight mode, if there is a temporary interruption (such as the phone is in an elevator or tunnel) then your app could keep on trying for as long as it is able - keep trying every minute while in the foreground, then when you switch to the background you know you have 10 minutes left, keep trying until the 10 minutes has nearly expired then post a local notification to the user notifying them that the app was unable to place the order due to lack of connectivity. If the user clicks on the notification and your app launches then the app will have the chance to retry again at that point.
If you still cannot make a connection then so be it, but you will have the chance to start the retry algorithm again. But at least you have notified the user their order has not gone through.
If what you need to know is if and when a data connection is available, I recommend inverting the process: rather then querying for a data connection, let your app be notified when a data connection is available. It's more efficient.
On this subject, I suggest using Reachability: you can make a call to know if a specific URL is accessible, and execute a block of code as soon as a connection is available.
Reachability *reach = [Reachability reacabilityWithHostName:#"www.myservice.com"];
...
reach.reachableBlock = ^(Reachability *reach) {
// Process the requests queue
// You should implement the method below
[self processQueue];
}
...
if ([reach isReachable]) {
// Upload the XML file to the server
// You should implement the method below
[self uploadToServer:myRequest];
} else {
// Enqueue your request somewhere, for example into an NSArray
// You should implement the method below
[self addToQueue:myRequest];
}
The above code is meant to be a showcase (it doesn't work as is), use it as reference. I can just say that the reach variable should be a class property or data member, and that it should be initialized once.
Also, if you enqueue your requests into an NSArray, be sure to do it in thread safe mode
Alternatively, Reachability can also notify via NSNotification when a connection is available - a different way to achieve the same result. Up to you to decide which one better fits with your needs.
I am developing an iPhone application which is completely based on web data.
If it is not connected to the internet, the application is of no use.
So, I want to terminate the application when connection is not found.
NSURL *tmpURl=[NSURL URLWithString:[NSString stringWithFormat:#"%#search.php",[iGolfAppDelegate getServerPath]]];
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:tmpURl];
con=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(con){
myWebData=[[NSMutableData data] retain];
} else {
//Yes I will provide two buttons on alertview "retry" & "close", & when user
//taps on "close" => application should terminate.
// i will send alertview & when user taps on button close then
// what to write for terminating application?
// Ok Ok. Don't terminate. User will terminate.
// user is owner of iPhone
// let him choose what to do
// wait till wifi connects
}
The question is how to terminate the application?
Is exit(0) only the option for terminating application or is there any other option available?
Apple is is absolutely clear about this topic:
There is no API provided for
gracefully terminating an iPhone
application. Under the iPhone OS, the
user presses the Home button to close
applications. Should your application
have conditions in which it cannot
provide its intended function, the
recommended approach is to display an
alert for the user that indicates the
nature of the problem and possible
actions the user could take - turning
on WiFi, enabling Location Services,
etc. Allow the user to terminate the
application at their own discretion.
See Technical Q&A QA1561
You might consider informing the user that they cannot use your application without an active network connection. Just terminating the application outright seems like a very unfriendly way of doing this; the user will simply see the app "disappear".
Every well-behaved app I've seen will at least give a notification before terminating.
I would advise you to reconsider for 3 reasons
It may appear that your app crashed.
The user may get an internet connection while your app is up. In this case a 'Retry' would be best.
I think Apple may actually not accept the app if it does that. It is for sure not what they would do if an Apple application needed an internet connection, and they do test to see what an app will do without a connection.
If (for whatever reason)you do want to do it you can use.
exit(0);
You could always just divide by zero. As a bonus, the implementation would reflect what a good idea this is.
Hope this helpful
[[NSThread mainThread] exit];
If you terminate it will look like your app has crashed!
Best to put up a message saying that there is no internet connection and give them an option to retry (in case they can get an internet connection), or choose to quite it themselves
You shouldn't do this. Take a look at "Stopping" in the Human Interface Guidelines as you could possibly fail for submitting an App that does this, or at the very least provide for a strange user experience.
The link also shows the correct way to handle this, as in the iTunes Music Store app.
Your App will be rejected if you terminate when you cannot reach the Internet.
Sorry.
-t