How to make network request faster in Swift - swift

I'm using Alamofire for network request. When I load a new viewController I make a new request in ViewDidAppear to get example url to images ect. When I make the request in ViewDidAppear there is a delay before the data appear, I also tried in ViewDidLoad the request was a little bit faster, but you can stil see the data appear after a small delay. It is okay that when a user access the viewController first time the user will see the data is loading, but is there a way to keep the data so that when a user navigate away from the controller, example when a user go back from a push in a navigationController and then navigate forward again without making the request to get the data again?
Here is one of my request in ViewDidAppear.
Hope you guys can help - Thank you
override func viewDidAppear(animated: Bool) {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
var parameters = [String: AnyObject]()
if(self.MySelf) {
parameters = ["userId": LoginViewController.CurrentUser.UserID as AnyObject]
}
else {
parameters = ["userId": self.UserID as AnyObject]
}
//GET posts
Alamofire.request(.POST, Config.ApiURL + "getUserPosts?token=" + LoginViewController.CurrentUser.Token, parameters: parameters as! [String : AnyObject]).responseJSON{ response in
print(response)
switch response.result {
case .Success(let data):
let json = JSON(data)
if let posts = json["post"]["data"].array {
self.postArray = posts
self.postArray = self.postArray.reverse()
self.navigationItem.title = json["user"]["firstname"].string! + " " + json["user"]["lastname"].string!
self.User = json["user"]
self.UserPic = self.User["photourl"].string!
}
else {
print("Array is empty")
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
self.ProfilePostColView?.reloadData()
}
}

Try making the network request in an earlier view controller - say a loading screen and then pass the response to this view controller.
Alternatively you could store the response in a cache service of sorts - when the user navigates back to this view controller you could check it already in the cache if so load it up to the view if not call the request.
Also making the network request in viewDidLoad will be faster as it called before viewDidAppear - but keep in mind viewDidLoad is only called once for a specific instance of a viewController where as viewDidAppear is called every time that instance is displayed again (eg. if it as the bottom of the navigation stack and the user presses back to it).
Keep the user in mind - you do not want to be chewing up their data so if you know the request will have the same response you only want to make the http request once.

Related

Get the URL response code before ViewController loads?

I am trying to get the status code of the URL before the MainViewController loads, now I know walk arounds but is it doable? I tried putting the code in willEnterForeground, didFinishLaunchingWithOptions and on init(), with the last one working from time to time.
Before anyone asks or thinks as to why I might do this, I am mostly wondering now, is there a faster way to get the response code etc.
let task = URLSession.shared.dataTask(with: url!) { _, response, _ in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
if httpResponse.statusCode == 404 {
print("404")
} else if httpResponse.statusCode == 200 {
print("200")
}
}
}
What you have to do is create a new view controller that will indicate that somethings is loading, then push/present the MainViewController on the success of the asynchronous method.
If you try to block the main thread until the status comes, the iOS watchdog will likely kill of your app. Source

In swift, how can I wait until a server response is received before I proceed?

I would like to only execute a segue if I get a certain response from the server. In swift, how can I wait until I get a response to continue?
Bottom line, you don't "wait" for the response, but rather simply specify what you want to happen when the response comes in. For example, if you want to perform a segue when some network request is done, you should employ the completion handler pattern.
The issue here is that you're probably accustomed to just hooking your UI control to a segue in Interface Builder. In our case, we don't want to do that, but rather we want to perform the network request, and then have its completion handler invoke the segue programmatically. So, we have to create a segue that can be performed programmatically and then hook your button up to an #IBAction that performs the network request and, if appropriate, performs the segue programmatically. But, note, there should be no segue hooked up to the button directly. We'll do that programmatically.
For example:
Define the segue to be between the two view controllers by control-dragging from the view controller icon in the bar above the first scene to the second scene:
Give that segue a storyboard identifier by selecting the segue and going to the "Attributes Inspector" tab:
Hook up the button (or whatever is going to trigger this segue) to an #IBAction.
Write an #IBAction that performs network request and, upon completion, programmatically invokes that segue:
#IBAction func didTapButton(_ sender: Any) {
let request = URLRequest(...). // prepare request however your app requires
let waitingView = showWaitingView() // present something so that the user knows some network request is in progress
// perform network request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// regardless of how we exit this, now that request is done, let's
// make sure to remove visual indication that network request was underway
defer {
DispatchQueue.main.async {
waitingView.removeFromSuperview()
}
}
// make sure there wasn't an error; you'll undoubtedly have additional
// criteria to apply here, but this is a start
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// parse and process the response however is appropriate in your case, e.g., if JSON:
//
// guard let responseObject = try? JSONSerialization.jsonObject(with data) else {
// // handle parsing error here
// return
// }
//
// // do whatever you want with the parsed JSON here
// do something with response
DispatchQueue.main.async {
performSegue(withIdentifier: "SegueToSceneTwo", sender: self)
}
}
task.resume()
}
/// Show some view so user knows network request is underway
///
/// You can do whatever you want here, but I'll blur the view and add `UIActivityIndicatorView`.
private func showWaitingView() -> UIView {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
effectView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(effectView)
NSLayoutConstraint.activateConstraints([
effectView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
effectView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
effectView.topAnchor.constraintEqualToAnchor(view.topAnchor),
effectView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
])
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
effectView.addSubview(spinner)
spinner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
spinner.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
spinner.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
])
spinner.startAnimating()
return effectView
}

swift 2 UITableView Refresh after Almofire

I know almofire works as a thread in the background so I put in my main class
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: "taskdataReadyFunc", name: "taskdataReady", object: nil)
and in my dataClass after the almofire finish:
init() {
Alamofire.request(.GET, urlString, parameters: parameters).responseJSON { response in
if response.result.isSuccess {
let json = JSON(response.result.value!)
let data = json.arrayValue
self.tasks = data
print(self.tasks)
}
// print(self.tasks)
print(String(self.tasks.count)+"before nc")
let nc = NSNotificationCenter.defaultCenter()
nc.postNotificationName("taskdataReady", object: nil)
}
that leads to thet function:
func taskdataReadyFunc (){
tableView.reloadData()
print("reload mision table" + String(taskDataClass.sharedInstance.tasks.count))
}
This worked fine when I start the app for the first time.
my problem is that I made a new task that adds a new task to the database and then tries to run the init of the dataClass again. at that stage, i can see the new task coming from the DB but it not make a table reload data.(if I close and open the app I see it)
how can I refresh my table view or maybe to close and open the controller from the start ?
I'm not sure what you're trying to do with self.tasks = data, but normal Alamofire usage combined with NSNotification would look like this:
request(.GET, urlString, parameters: parameters).responseJSON { response in
guard let json = response.result.value else { return } // handle error
let notification = NSNotification(name: "taskDataReady", object: nil, userInfo: ["taskData" : json])
NSNotificationCenter.defaultCenter().postNotification(notification)
}
Then you would unwrap the JSON value from the notification. I'd actually recommend decoding the JSON into an actual type from within the Alamofire completion handler and encapsulating that within an NSNotification, so you don't have to do additional work in the view controller.
the sharedInstance is create only one time when you first call it.
its called singleTon.
that means that if the init() function fill the variable in the class the sharedInstance will not be update.
so, in the init class, even in the first run update the sharedInstance and not the variable.
example:
//self.tasks = data
taskDataClass.sharedInstance.tasks = data

View Dependent on network Request

In order to fully render my View from my View Controller, I need to have a response from a network request.
I have been trying to do this is many different ways, but have been unsuccessful each time.
Originally, I had gotten it to work by making a "synchronous" network request prior to calling any methods to render the View. However, the compiler is warning me that the synchronous network requests are deprecated as of ios 8.
What is the best way to accomplish this in the most performant way?
I have tried:
override func loadView() {
dispatch_sync(dispatch_get_main_queue()){
// GET the Markup
let url = NSURL(string: self.PageURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let RequiredViewData = session.dataTaskWithRequest(request) {(data, response, error) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
self.RequiredViewJSON = json
self.view = UIView(frame: UIScreen.mainScreen().bounds)
print(data)
} catch {
print("error serializing JSON: \(error)")
}
}
RequiredViewData.resume()
}
}
But that just makes my app render as a blank screen.
Essentially what I need to accomplish is this:
Make a network request and receive the response before any view rendering can occur.
Thanks in Advance!
I never really tried to override loadView nor know if you should, but I think what you need to do is call super in this case to get your view to render again.
Edit
Also per your comment I put the main thread call "after" you get the call back from the NSURLSession. I might have a } in the wrong spot but should get you close enough.
override func loadView() {
// GET the Markup
let url = NSURL(string: self.PageURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let RequiredViewData = session.dataTaskWithRequest(request) {(data, response, error) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
self.RequiredViewJSON = json
dispatch_sync(dispatch_get_main_queue()){
//You may also want to try commenting this out unless you are intentionally creating a blank view.
self.view = UIView(frame: UIScreen.mainScreen().bounds)
print(data)
//call super after you get what you need
super.loadView()
}
} catch {
print("error serializing JSON: \(error)")
}
}
RequiredViewData.resume()
}
}
Hopefully that helps.
The view controller should handle all of this networking logic in viewDidLoad or viewWillAppear, not in loadView. I'd suggest setting a loading state an initial empty state on the subview, then once you have what you need, update the view with that data. You may need to call setNeedsLayout on the view to update for the new data.

Correct asynchronous Authentication while keeping a responsive UI

What it's supposed to be
I have a username field. When the username is entered and the sendButton is clicked, the userdata is fetched with a asynchronousRequest as a JSON file.
After the sendButton is clicked, I want to display an ActivityIndicator.
The UI shall still be responsive, while the request is made.
How it is now
I click the sendButton and the UI freezes. Even the ActivityIndicator does NOT get displayed.
The code
LoginVC:
func buttonTouchedUpInside(sender: UIButton) {
toggleActivityIndicatorVisibilityOn(true)
LoginManager.sharedInstance.checkUserForCredentials(username: textFieldLogin.text, password: "")
toggleActivityIndicatorVisibilityOn(false)
}
func loginManagerDidFinishAuthenticationForUser(userData: [String:String]?){
// Delegate Method, which works with the fetched userData.
}
LoginManager
func checkUserForCredentials(#username: String ,password: String) -> Void {
let url = NSURL(string: "\(Config.checkCredentialsUrl)username=\(username)")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: .mainQueue()) { (response, data, error) -> Void in
if error != nil {
//Display error-message
}
var error : NSError?
let json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as? [String:String]
self.delegate?.loginManagerDidFinishAuthenticationForUser(json)
}
}
In short: I want the request to be made in background, that the Activityindicator is shown and the UI stays responsive. After the asynchronous request successfully fetched the json, the delegate method shall be called
The second line of code in the buttonTouchedUpInside method, which reads LoginManager.sharedInstance.checkUserForCredentials(username: textFieldLogin.text, password: "") is calling an asynchronous function within it, which means that it is not blocking the next line of code... which is the one that (I am guessing) triggers your loading screen to become invisible again.
Basically, your loading screen is showing up, but it is immediately being hidden again. To fix at least the part with your loading screen, put the third line of code in the buttonTouchedUpInside function in the callback method loginManagerDidFinishAuthenticationForUser instead.