Repeat an asynchronous request if it fails? - ios

I am writing an ios app that relies on being able to tell when a user is connected to wifi and, when he or she connects or disconnects, send an asynchronous request using alamo fire.
The first time I connect, my asynchronous succeeds.
However, after I first connect, any toggling of the wifi results in 404s.
I suspect this is because I am sending the request as soon as the user connects/disconnects, meaning that for a brief moment he or she has no internet service.
My question is, can I repeat the request if it fails or is it possible to "cache" the requests I want to make and wait until the user has internet connection to make them?

There are many solutions to solve this. One is to call the download method recursively again and so implementing an automatic retry mechanism on errors:
func downloadSomething() {
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
if let error = error {
log(error)
self.downloadSomething() // recursive call to downloadSomething
} else {
// do something on success
}
}
}
You can extend this by:
showing the user also an altert view asking him if he want's to retry
the download or not before retrying the download. (depending on your
UI strategy on network errors)
a specified count of automatic re-trys and then ask the user.
checking the error status code and then depending on the code do
different network error handling strategies...
etc...

I think there is no needed to re-invented apple code like reachability or this swift reachability porting. You can able to check if a user is connected to the net or wifi very easily:
class func hasConnectivity() -> Bool {
let reachability: Reachability = Reachability.reachabilityForInternetConnection()
let networkStatus: Int = reachability.currentReachabilityStatus().rawValue
return networkStatus != 0
}
For a Wi-Fi connection:
(reachability.currentReachabilityStatus().value == ReachableViaWiFi.value)

Related

iOS How to check how many request was sent for server, parse swift 4

I have something strange with my parse server, I start to check Analytics toll at the parse, and I saw during some period I have more than600 request per minutes, to the server, in my opinion, is not possible because only I am, testing app.
At image you can see a trend, I have 10-14 requests, and immediately I get 600?
How I can check how many requests are sent my app?
You can use a proxy to catch your request
-> Charles is a really useful tool for this
If you want count in your app how many are sent, it depends on how you organised your code.
-> you could create a counter for this (in your custom "ReqeustMaker" create a static variable or even in in a shared instance class if you need more fancy stuff)
Using the following code you can trap all the calls, and find them all on Xcode debug console:
class MyURLProtocol: NSURLProtocol {
class func canInit(with request: URLRequest) -> Bool {
print("Requests : \(request.url?.absoluteString ?? "")")
return false
}
}
and call this when app is loading:
URLProtocol.registerClass(MyURLProtocol.self)

Pattern for retrying URLSession dataTask?

I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.

Intercepting EVERY response with Alamofire

I'm just exploring using Alamofire and it is excellent but I'd like to do something that I feel is possible just not sure how.
Our authentication with the server uses one-time-use bearer tokens. So for every request made I have to store the new token sent down with that request.
What I'd like to do is intercept every response that comes back and check the Authorisation header. Save it to disk and then forward to the place waiting for the actual data.
Is this possible with Alamofire?
If so, please could you point me in the right direction.
Thanks
OK, after a bit of searching the github and head scratching I decided to create a new response serialiser by extending the Request type.
I created a new saveAuth() block like so...
extension Request {
public static func AuthSaver() -> ResponseSerializer<Bool, NSError> {
return ResponseSerializer { request, response, data, error in
guard error == nil else { return .Failure(error!) }
if let auth = response?.allHeaderFields["Authorization"] as? String {
Router.OAuthToken = auth // this uses a didset on the Router to save to keychain
}
return .Success(true)
}
}
public func saveAuth() -> Self {
return response(responseSerializer: Request.AuthSaver()) {_ in}
}
}
I can call it like...
Alamofire.request(Router.Search(query: query))
.validate()
.responseSaveAuth() // this line
.responseJSON {
response in
// ...
}
It still requires adding in each place that I want to strip out the newly sent auth token but it means I can choose when not to do it also and it's a single line of code.
It's maybe not the most elegant code in the extension (I'm still getting to grips with it all) but it makes it much easier to save the authentication each time.
I have solved this by only having one place in my app that sends network requests. Basically, I have a "network manager" that builds up NSURLRequests and pipes them to one function that actually sends the request (in my case it's an NSOperation sub class). That way I have only one location that I'm reading responses from.

NSURLSession Performance - Probable race conditions or blocked threads?

I've ran into a bit of a performance issue with my iOS app, this is my first time working with NSURLSession and NSURLRequest, and although I've tried to inform myself as much as I can, I've hit a wall trying to debug a performance issue I'm facing.
So here's what I got: I've got an iOS 9 app written in Swift 2, I'm communicating with a NodeJS/Express server through Get, Post and Put Http requests, utilizing NSURLRequest and NSURLMutableRequest. I'm sending requests to fetch a group of objects (All together no more than 12000 bytes), however the requests are taking a significant amount of time (sometimes up to a minute). I've added logging to the nodeJs server and I can see that the requests take no longer than 30 milliseconds to be processed.
Note: I'm unsure if this is relevant, but I'm using a singleton "helper "class to make all my api requests and parse the results (saving authentication tokens, parsing JSON objects and saving them to Core Data, saving user preferences to NSUserDefaults, etc), I'm using a singleton so i can access it statically and I'm parsing all the data without saving anything in singleton's properties other than the URL of the server and the NSURLSession.
Here's what my code looks like.
//On initialization of the helper class
private let session = NSURLSession.sharedSession()
func getAllObjects() {
let route = "api/someRoute"
let request = getRequest(route)
request.timeoutInterval = httpTimeout
session.dataTaskWithRequest(request, completionHandler: ResultingObjects).resume()
}
The getRequest method returns a formatted NSMutableURLRequest, shown
here:
func getRequest(route: String) -> NSMutableURLRequest {
let request = NSMutableURLRequest()
request.URL = NSURL(string: "\(serverUrl)/\(route)")!
request.HTTPMethod = "GET"
request.addValue("Bearer \(self.AuthenticationToken()!)", forHTTPHeaderField: "Authorization")
return request
}
The completion handler will parse the objects returned and notify
the main thread with the resulting parsed objects, as so:
private func ResultingObjects(data: NSData?, response: NSURLResponse?, error: NSError?) {
if let d = data {
if !isAuthorized(d){
return
}
do {
if let JSON = try NSJSONSerialization.JSONObjectWithData(d, options: []) as? NSDictionary {
if let message = JSON["message"] as? String {
if message == "Empty result" {
//- Return notification to be handled in main thread
notifyMainThread(NoObjectsFetched, payload: nil)
return
}
}
if let objcts = JSON["SomeObjects"] as? NSArray {
if let SomeObjects = parseResultingObjects(objcts) {
//- Return notification to be handled in main thread
notifyMainThread(ObjectsFetched, payload: ["payload": SomeObjects])
}
return
}
}
}
catch {
print("Error getting resulting objects")
}
}
else if let e = error {
print("\(e), could not process GET request")
}
}
I've also tried parsing the resulting objects on the main thread but
that doesn't seem to make a difference.
If you are curious, this is how I'm sending data to the main thread:
private func notifyMainThread(notification: String, payload: AnyObject?) {
dispatch_async(dispatch_get_main_queue(), {
if let p = payload {
NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil,
userInfo: p as! [String: [MYMODEL]])
}
else {
NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil)
}
});
}
What I've found out:
Nothing makes sense! I've attempted debugging this but I cant really pin point what the issue is, When the debugger hits my "getAllObjects" method, it can take a good few seconds (up to 45 seconds) before the server logs that it received and processed the request (which it usually takes around 30 milliseconds). As far as I can tell, this happens for all requests types. Also, once the application gets the data back (super fast), it takes a long time (around 4 seconds) to parse it, and its only around 11kbs.
I've also attempted to change the requests cache policy in case the application was checking the validity of the cached records with the server, I used ReloadIgnoringLocalAndRemoteCachedData which didn't work either.
Now, this sounds like a memory leak
If I pause the application at any point (after using it for a few minutes), I can see a concerning number of threads. I'm honestly not too familiar with IOS so I'm unsure if this threads are from the simulator or if they all belong to the app, the app streams video contents (which has no latency issues) usin the AVPlayer class and I believe that many of this threads are related to this, however I'm unsure if this is normal, here's a screenshot of what I mean (Note the scroll bar T_T) Screenshot
Could it be that I've got a memory leak or some zombie threads considerably slowing the performance of my app? The only really noticeable delay happens only on HTTP requests which is quite odd, no other part of my UI lags, and no other feature in my app suffers from performance issues (even streaming video contents from a url).
What would be the best way to profile this performance issues to pin point the source of the problem?
UPDATE 1:
Thanks to the suggestions by Scriptable, I've managed to address the threading issue (caused by multiple AVPlayers doing their thing). The performance of the requests however are not solved.
Its worth pointing out, the server is physically in the same country as where I'm making the requests from, when making requests from the browser or form the Command Line the requests are almost immediate.
Also, when I randomly pause the app (while I wait to the request to happen) I can see a 'mach_msg_trap' in some of the threads, I'm not familiar with this but I believe this might be a race condition? or a deadlock?

Polling for Response from BLE Adapter in Swift

Using a Wi-Fi socket based adapter, I can successfully poll for a response like so:
func writeMessageWithResponse(message: String) -> [String] {
self.waitingForResponse = true
let runLoop = NSRunLoop.currentRunLoop()
if self.response != nil {
self.response?.removeAll()
}
writeMessage(message) // this will set self.waitingForResponse to false when a response is received
while self.waitingForResponse && runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()) {
// waiting for flag (self.waitingForResponse) to be set
}
return self.response!
}
When I use this same code with a CBCentralManager BLE connection, the main thread is blocked and does not receive the response from the connection. I've tried changing the CBCentralManager to a different queue, but I get the same results.
Does anybody have an idea how to wait in a loop and still be able to receive a BLE response? I know a response is being sent, but the thread is blocked and not reading it.
Using an async function with a completionHandler won't work for this use case because I need a reusable function that can issue a chain of commands that each depend on the result of the last response.
The CBCentralManager and CBPeripheral APIs are not really designed for this. You should be using the methods provided by CBPeripheralDelegate and CBCentralManagerDelegate protocols. Initialize your central manager with a dedicated queue so that you can listen for responses to your peripherals and act on that data as appropriate.

Resources