Problem
I'm currently grabbing weather forecast information with [NSData dataWithContentsOfURL: url]. The problem with this is that if there are errors during the URL fetch, I just get back a nil.
An alternative to the method [NSData dataWithContentsOfURL:options:error:] will provide error info to me, but what my calling function wants is the data, not a possible error that it must diagnose and deal with.
Requirements
What I'd like is a single function that a client can call to grab a URL that "absolutely will not stop" until the URL has been loaded.
I don't want the function's clients to need to think about:
Timeouts.
Internet connections being unavailable or not.
The device being locked or the app moving in to the background.
The actual site in question being down.
Other sources of error.
Other design objectives
The function should treat the device and remote site nicely – it should not swamp it with requests resends that will certainly fail, for example.
The caller should be able to abort the attempt if it desires, so it'll want to have a handle to the request allowing it to kill it off.
The function should be asynchronous, taking a block to handle the result when it eventually arrives.
For extra marks a method for the calling function to be sent error diagnostics would be nice. Again, I think a block would work nicely for this. The needn't do anything about the error, because the function isn't going to give up, but it can use it to provide useful feedback to a user. For example, to allay their concerns, or prompt them to take remedial action (turn networking back on, for example).
Possible interface
So the a client call to the function might go like this:
_currentGrabber = [TenaciousURLGrabber
grabberForURL: myURL
withCompletionAndDiagnosticsHandler:
^(NSData* finalData, ErrorObject *error){
if(data)
{
// Update my UI using data.
}
else
{
// Update my UI to show `[error localisedError];`
}
}];
If the client gets bored or decides that the fetch isn't worth it any more, it can do:
[_currentGrabber invalidate];
Implementation thoughts.
It'd be great if this pretty much already exists. Otherwise, I'm interested in suggestions on implementing this functionality.
I should probably be making use of NSURLSession instead of the older NSURLConnection. The possibility of background (out of process) downloads looks useful? Any tips beyond this?
The function should use SCNetworkReachability as demonstrated in the Reachability sample application following failure to determine when its worth a retry attempt.
Related
We set timeout interval for a request by NSMutableURLRequest timeoutInterval. As Apple's document described, it specifies the limit between packets, not the whole request. When we analyse our requests logs, some timeout request exceeded the seconds we set to timeoutInterval. We need timeout the requests accurately.
By reading document and blogs, the timeoutIntervalForRequest property in NSURLSessionConfiguration is the same as timeoutInterval. But the timeoutIntervalForResource property seems fit our requirement.
However, Mattt says in objc.io that timeoutIntervalForResource "should only really be used for background transfers". Can it be used in normal request? Such as query user info. Is it appropriate in this situation?
Thanks very much.
It can be used, but it rarely makes sense to do so.
The expected user experience from an iOS app is that when the user asks to download or view some web-based resource, the fetch should continue, retrying/resuming as needed, until the user explicitly cancels it.
That said, if you're talking about fetching something that isn't requested by the user, or if you are fetching additional optional data that you can live without, adding a resource timeout is probably fine. I'm not sure why you would bother to cancel it, though. After all, if you've already spent the network bandwidth to download half of the data, it probably makes sense to let it finish, or else that time is wasted.
Instead, it is usually better to time out any UI that is blocked by the fetch, if applicable, but continue fetching the request and cache it. That way, the next time the user does something that would cause a similar fetch to occur, you already have the data.
The only exception I can think of would be fetching video fragments or something similar, where if it takes too long, you need to abort the transfer and switch to a different, lower-quality stream. But in most apps, that should be handled by the HLS support in iOS itself, so you don't have to manage that.
We use an NSURLSession to download data in the background, and have timeoutIntervalForResource defined so it will timeout if it takes too long, but if, for whatever reason, the source server doesn't exist then it still sits and waits. Is there any way to get it to abort immediately, or 'ask' the NSURLSessionDownloadTask if anything has been downloaded yet?
Failing that, what would be the best way of performing a pre-check to ensure a server exists before trying to download data from it?
These servers may be out of our control so we can't place a small file to download to check availability. The only file we may not about could be a sizeable video, for example.
You can indeed ask the task about its status. First, check the response property. If that is nil, then you haven't gotten the first packet from the server. If that is non-nil, use countOfBytesExpectedToReceive and countOfBytesReceived as needed to determine progress.
I should also note that these properties all support KVO, AFAIK.
You could also perform an explicit DNS lookup prior to scheduling the background request if you'd prefer, with the caveat that doing so would prevent you from scheduling something that might actually work if the user's Internet connection comes back online in the meantime. :-)
I have an exception handler that runs when my code crashes, but I also need to send a msg to the server (parse.com) when this happens to let the server know the player has stopped playing,
void onUncaughtException(NSException *exception)
{
NSLog(#"uncaught exception: %#", exception.description);
[self playerLoggedOut];
}
The playerloggedOut line gives the error of undeclared identifier self.
How can I run the playerLoggedOut method when the exception happens?
How can I run the playerLoggedOut method when the exception happens?
You're not going to be able to start some lengthy process like establishing a network connection and sending a message. Your best strategy might be to save the information and send it when the app starts up again so that the server can update its records or whatever. Alternatively, have the client check in with the server every t seconds; if the server doesn't hear from the client within some interval like 2t, it assumes that the client has stopped functioning.
First of all: self is unknown, because you are not in a method, but a function. Functions do not run in an object context, therefore do not know self.
As mentioned before by Caleb, you should have a watch dog on server-side that automatically logs a player out, if the server gets no messages from the client for a while (in terms of seconds). To prevent from being logged out automatically, when the user is deactive (but still playing), you can implement a heart beat on the client using an instance of NSTimer.
As others have stated, don't try to do expensive things like talk to the Parse server in your exception handler.
In regards to what you are actually trying to achieve, you might want to re-think your architecture.
In the sometimes-connected world of mobile devices you will have your connection come and go. Instead of a boolean flag of IsLoggedIn, consider a UTC Date Time of LastUserActivity, and use a rule that says they are considered logged in if that last activity is in the last 5 minutes, or whatever is suitable.
If all access to Parse is done via Cloud Code then you could easily add a function call that sets the LastUserActivity to each method, then you can also avoid issues with time sync since you'll always be using the server's clock.
If you want to get a list of all online users, you can then just query for where LastUserActivity is greater than 5 minutes ago (or whatever limit you set).
Does anyone know of any good resources that fully explain how functions and results will fire in an Adobe AIR app where multiple things are happening at once?
As a simple test, I've created a single service that I just keep changing the url of, then issuing a send(). It seems that no matter how many send() calls I put in, all of these get executed before the 'result' eventListener function gets called for the first time.
Is this how it works? i.e. the current function gets fully executed, with the async returns queueing up to be processed after AIR has finished what it's currently doing.
Likewise, if the user does something while all this is going on, I presume their request goes to the back of the queue as well?
All that makes sense, but I'm just wondering if it's documented anywhere.
While I'm on one, is it recommended practice to reuse the same HTTPService in this way, or is it better to create one for each concurrent transaction? Just because it works, doesn't mean it's the right thing to do...
I'm not aware of any documentation that explains this, but I can confirm that code blocks get executed before async calls are made, or at least before their result is being processed. If it didn't work that way, you would for instance not always be able to attach a responder to a token of a service call, because the result might already have been processed.
var token:AsyncToken = myService.someMethod();
token.addResponder(new Responder(resultHandler, faultHandler));
Developers coming from other platforms find this strange as they would expect the assignment of the responder to be too late.
So while I don't have an official explanation about the technical details inside the Flash Player, I can assure that it works this way.
If the user does something while a call is pending, the new request will indeed just be added as a new asynchronous call. Note that we can't realy speak of a queue, as there is no guarantee that the response of the first call comes in before the response of the second call. This depends on how much time the actual requests take.
You can perfectly reuse an HTTPService instance.
PS: Based on this, we were able to build the Operation API in Spring ActionScript. It is basically an API that allows you to execute asynchronous processes in a uniform way, without having to worry about the details of the actual async process.
The following code executes an async process and attaches a handler to it. This is also something that puzzles many developers at first, for reasons similar to the asyncToken situation.
var operation:IOperation = doSomeOperation();
operation.addCompleteListener(aCompleteHandler);
operation.addErrorListener(anErrorHandler);
I have not been able to find a way to cancel/terminate asynchronous read operation after successful HttpWebRequest. There is no way to set timeout, ThreadPool.RegisterWaitForSingleObject is not working too. And closing the underlying socket is not an option too because neither HttpWebRequest/Reponse provide access to it.
Edit:
Sadly this approach that Sunny suggestet works only for HttpWebRequest.BeginGetResponse. For the stream that you get after GetResponseStream() for some reason RegisterWaitForSingleObject is not working - the callback is never called.
The situation is the following:
I got an application that uses HttpGetRequest. It is build by using the default MSDN example for async httpwebrequest. Getting response is working like a charm. But on rare occasions the server that my httpwebrequest is connecting to forgets to close the socket. So I am hung on a infinite read from BeginRead.
In some rare occasions the other server forget
Why not RegisterWaitForSingleObject? You can use it for time out, and in the handler you can call request.Abort().
Here's an example. Btw, I have used simial approach before I found this article in the past, and it worked like a charm.
NOTE: The real end of the operation will happen on the next Stream.Read (of only the request/response was async) or Stream.EndRead (if the reading is async as well). You still need to capture the exception thrown in the EndXXXX handler.
EDIT: If the RegisterWaitForSingleObject never get called, then you have another issue, which is most probably a ThreadPool having not enough free threads. The callback for RegisterWaitForSingle object is called on a ThreadPool thread, and if there is no a free thread in the pool, it will never get called. You have some solutions:
Make your pages asynchronous. Why? How.
Change the max threads in the pool.
In any case, increasing the pool size will not help you too much. You have to inspect your code for resource (stream) leaks, and make sure that you not only call request.Abort(), but also close the response. Make sure than in your callbacks you use the proper EndXXX method, etc.
I nazdrave :)