Alamofire requests are slow - ios

I'm using Alamofire in my project, my problem is requests takes a long while to load (10 seconds at least), is there a way to speed it up?
That's one of the requests I'm handling
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, TripsEndPointURL, parameters: nil)
.responseJSON { (request, response, JSONData, error) in
if (error != nil) {
NSLog("Error: \(error)")
}
else {
TripsTableList = JSON(JSONData!)
self.TripsTableView.reloadData()
}
}
}

You're using Alamofire correctly. I'd imagine this is a problem with the server, or your connection. Try replacing the endpoint URL with the url of any small image, from a different server, just to experiment with downloading it. If it's still slow, it's your connection. If it's faster, it's the server.

Related

How to retry URL requests indefinitely until success with Alamofire 5?

Is there any recommendation on using Alamofire's RequestRetrier to keep retrying a request until it succeeds?
The issue I'm running into is every time a retry happens it increases the memory of the app. I have a scenario that a user might make a request offline and it should eventually succeed when they regain connection.
Is it not good practice to continually retry a request? Should I use a Timer instead to to recreate the network call if it fails a few times? Should I use some other mechanism that waits for network activity and then sends up a queue of requests (any suggestions on what that would be would be appreciated!)?
Here is an example to see the memory explode. You just need to put your iPhone in airplane mode (so there is no connection) and the memory will keep going up rapidly since the call will happen a lot in this case.
import UIKit
import Alamofire
class ViewController: UIViewController {
let session = Session(configuration: .default)
override func viewDidLoad() {
super.viewDidLoad()
sendRequest()
}
func sendRequest() {
session.request("https://www.google.com", method: .get, interceptor: RetryHandler())
.responseData { (responseData) in
switch responseData.result {
case .success(let data):
print(String(data: data, encoding: .utf8) ?? "nope")
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
class RetryHandler: RequestInterceptor {
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: #escaping (RetryResult) -> Void) {
// keep retrying on failure
completion(.retry)
}
}(

UITableView loads cells before API call is completed

I am working on this app that helps me run some NLP on tweets & display results in a feed using a TableView.
Up to today, my app was running all the NLP on-device with a custom model built with CreateML & Apple's NaturalLanguage framework. When I would open the app, the tweets would be analyzed & show the results in the feed.
To increase the accuracy of results, I set up my own API & now make a call to that API to do some extra analysis. The issue now is that when I open the app, there is some kind of race condition. The feed does not show anything until I refresh. In the console, I see that the feed is done running fetchAndAnalyze() that gets the results while the API call in tripleCheckSentiment() is not completed.
Here is some explanation around the architecture.
NetworkingAPI (only the relevant code):
// This function makes a call to the Twitter API & returns a JSON of a user's tweets.
static func getUserTimeline(screenName: String, completion: #escaping (JSON) -> Void) {
client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
if connectionError != nil {
print("Error: \(connectionError)")
}
do {
let json = try JSON(data: data!)
completion(json)
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
}
}
}
// This function makes a call to my API & checks the sentiment of a Tweet.
static func checkNegativeSentiment(tweet: Tweet, completion: #escaping (JSON) -> Void) {
let headers: HTTPHeaders = ["Content-Type": "application/json"]
AF.request(apiURL, method: .post, parameters: tweet, encoder: JSONParameterEncoder.default, headers: headers).response {
response in
do {
let json = try JSON(data: response.data!)
completion(json)
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
completion(JSON.init(parseJSON: "API OFFLINE."))
}
}
}
TweetManager (only the relevant code):
// This function is called from the app's feed to retrieve the most recent tweets.
func fetchTweets(completion: #escaping (Bool) -> Void) {
for friend in Common.listOfFriends {
NetworkingAPI.getUserTimeline(screenName: friend.handle, completion: {
success in
self.parseTweets() // This puts all the tweets returned in success in a list.
self.analyze() // Runs some NLP on the tweets.
completion(true)
})
}
}
func analyze() {
for tweet in listOfTweets {
// Does some on-device NLP using a model created with CreateML ...
if sentimentScore == "0" { // That is the tweet is negative.
doubleCheckSentiment(tweet: tweet)
}
}
}
func doubleCheckSentiment(tweet: Tweet) {
// Does some on-device NLP using Apple's generic framework NaturalLanguage.
if sentimentScore <= -0.8 { // Once again, the tweet is negative.
tripleCheckSentiment(tweet: tweet)
}
}
func tripleCheckSentiment(tweet: Tweet) {
NetworkingAPI.checkNegativeAzureSentiment(tweet: tweet, completion: {
json in
if json["value"]["sentiment"].int! == 2 { // We confirm the tweet is negative.
Common.negativeTweets.append(tweet)
}
}
}
FeedVC (only the relevant code):
// This function gets called when the view appears & at a bunch of different occasions.
func fetchAndAnalyze() {
var friendsAnalyzed = 0
tweetManager.fetchTweets(completion: {
success in
friendsAnalyzed += 1 // Every time completion hits, it means one friend was analyzed.
if friendsAnalyzed == Common.listOfFriends.count { // Done analyzing all friends.
self.tableView.reloadData() // Refresh & show the tweets in Common.negativeTweets in table.
}
}
I know this is long & I deeply apologize but if I could get some help on this, I would really appreciate it! By the way, excuse my use of #escaping & all that, I am fairly new to handling asynchronous API calls.
Thanks!
**EDIT, after implementing jawadAli's solution which works in some cases for some reason, I notice the following pattern: **
Imagine I add a friend to my listOfFriends. Then I refresh, which calls fetchAndAnalyze(). We see in the log REFRESH CALLED. & by the end of the function call that no negative tweets were found. Right after this happened, we get a result from our API call that one tweet was found negative.
If I refresh again, then that tweet is displayed. Any clue?
There is an issue with this function. On first transection of for loop your completion get fired ..
func fetchTweets(completion: #escaping (Bool) -> Void) {
let myGroup = DispatchGroup()
for friend in Common.listOfFriends {
myGroup.enter()
NetworkingAPI.getUserTimeline(screenName: friend.handle, completion: {
success in
self.parseTweets()
self.analyze()
myGroup.leave()
})
}
}
myGroup.notify(queue: DispatchQueue.main) {
completion(true)
})
Also reload data on Main thread
DispatchQueue.main.async {
self.tableView.reloadData()
}
NOTE: You need to handle success and failure case accordingly.. i am just giving an idea how to use dispatchGroup to sync calls ...

What is the better way to call multiple services in iOS?

I have 5 different services requests to load into same UItableView for each cells.
What is the best way to approach in order to do this.
https://example.com/?api/service1
https://example.com/?api/service2
https://example.com/?api/service3
https://example.com/?api/service4
https://example.com/?api/service5
let url = "https://example.com/?api/service1
Alamofire.request(url, method: .get, parameters:nil encoding: JSONEncoding.default, headers: nil)
.responseJSON { response in
print(response.result.value as Any) // result of response serialization
}
repeat the same Alamofire five times with different services name there is another way to implement it.
Look at using a DispatchGroup to perform multiple async requests and wait for them all to complete.
For each task you call group.enter() and in its completion handler when you know that request has finished you call group.leave(). Then there is a notify method which will wait for all requests to call leave to tell you that they have all finished.
I have created an example in a Playground (which will fail with errors because of the URL's used)
import UIKit
import PlaygroundSupport
let serviceLinks = [
"https://example.com/?api/service1",
"https://example.com/?api/service2",
"https://example.com/?api/service3",
"https://example.com/?api/service4",
"https://example.com/?api/service5"
]
// utility as I've not got alamofire setup
func get(to urlString: String, completion: #escaping (Data?, URLResponse?, Error?) -> Void) {
let url = URL(string: urlString)!
let session = URLSession.shared
let task = session.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
}
let group = DispatchGroup()
for link in serviceLinks {
group.enter() // add an item to the list
get(to: link) { data, response, error in
// handle the response, process data, assign to property
print(data, response, error)
group.leave() // tell the group your finished with this one
}
}
group.notify(queue: .main) {
//all requests are done, data should be set
print("all done")
}
PlaygroundPage.current.needsIndefiniteExecution = true
You probably won't be able to just loop through the URL's like I have though because the handling of each service is probably different. You'll need to tweak it based on your needs.
There is alot more information about DispatchGroups available online such as this article

Super long wifi request travel time on iPhone 8 / iPhone X

Not sure if it's ok to ask this here but I'm now confronting this frustrating problem and would like to ask for opinion on how to deal with this.
From some of the forum and discussions, looks like iPhone 8 and iPhone X has super slow internet issue when using wifi and running on iOS 11.4 / 11.4.1, and this results to the request travel time rediculously increases to almost 55 seconds (e.g. Hello world pinging test API) for a very simple request in my app. If I turn the wifi off and use 4G instead, the same request travel time is only 2 seconds(the API server is in the US while the app is oversea, so generally bearable), and the same request only travels 2 - 3 seconds on iPhone 6 plus running on iOS 11.2 / 11.4.1 in wifi mode.
I guess this is more a hardware or system side bug and on the app side we may not be able to do anything about this. However, as our client users who use iPhone 8 are unhappy about the waiting time and insist on solving it, and I also found that if I call the hello world API from safari browser of the iPhone, things are not that bad. Therefore, I would like to know if there is anything the app side can do (ex. detect this issue and do the workaround) to fix this or sooth the awful user experiences?
P.S. My app is written in Swift and I don't use third party library such as Alamofire to manage requests, simply use the build-in functionalities in Foundation. I would like to post some code on my sending request here, even if this may not help much.
func sendRequest(request: URLRequest, completion: #escaping (Int, Data?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else {
print("HTTP request error=\(String(describing: error))")
// use 999 to represend unknown error
completion(HTTPHelper.DEFAULT_STATUS_CODE, nil, error)
return
}
guard let httpStatus = response as? HTTPURLResponse else { // check for http errors
return
}
if(httpStatus.statusCode != 200) {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print("error serializing JSON: \(error)")
}
let responseString = String(data: data, encoding: .utf8) ?? ""
print(responseString)
completion(httpStatus.statusCode, data, nil)
}
task.resume()
}
And this is the function where the above request is called, the request travel time is the time elapsed between two [TIME FLAG]:
func test(completion: #escaping (Bool) -> Void) {
let url = httpHelper.myUrlBuilder()
let request = httpHelper.wrapGetRequest(url: url, withToken: .none)
NSLog("[TIME FLAG] Test request sent")
httpHelper.sendRequest(request: request as URLRequest) { statusCode, data, error in
NSLog("[TIME FLAG] Test response get")
guard error == nil && self.httpHelper.validateStatusCode(statusCode: statusCode) else {
completion(false)
return
}
completion(true)
}
}
Thanks for any kind of answers and feedbacks.

Why I sometime get 'The connection was lost' error?

so, I am making an app and want to GET data from my own server using alamofire. but I don't know why, I SOMETIME get the error
HTTP load failed (error code: -1005 [1:57]) .
finished with error -
code: -1005. error: The network connection was lost.
and this only happen when I try to get data to my own server. I try to compare to get data from other server, from darksky API, and I never get the error. here is the code I use to get data from my own server and to darksky API.
the error usually occurred when .....
after I start the app, and I push the home button ( so the app enter the background state), and when I back to the app, and at this time, I usually get the error.
so maybe I got the error when prepareDataBeforeEnterForeground is triggered. not always happened, and I don't know why it happened only to my server, and I need to know how to fix this.
does the error comes from my code or from the server?
import UIKit
import Alamofire
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getData()
getPrepareDataBeforeEnterForeground() // using notification center
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getData()
}
func getData() {
// to make request to my own server OR to DarkSky API
let url = URL(string: "https://")
let parameters : [String:String] = ["xxx": "xxxx"]
Alamofire.request(url!, method: .get, parameters: parameters).responseJSON { (response) in
switch response.result {
case .failure(let error):
print("Error: \(error.localizedDescription)")
case .success(let value) :
// parse the data
}
}
}
func getPrepareDataBeforeEnterForeground() {
// to trigger some function after the enter the background state
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.prepareDataBeforeEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func prepareDataBeforeEnterForeground() {
getData()
}
}
For reference, your error code correlates to:
kCFURLErrorNetworkConnectionLost = -1005
From my own personal experience, I get this error when the parameters I'm trying to send to the server are incorrect or malformed.
In your case, I can't seem to see where you are accepting parameters or defining it, so that might be your issue.
If its coming from your server, you will have to check the error logs for the server software (Apache, nginx, etc) (if you have them).
Here is an example of a valid request:
let parameters: Parameters = [
"foo": "bar",
"baz": ["a", 1],
"qux": [
"x": 1,
"y": 2,
"z": 3
]
]
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters)
To add on, it doesn't appear that you are sending any headers either, so you could be causing your server to reject the request completely.

Resources