Best place to make network calls - ios

Network Call :-
static func getProfile(parameters:[String:AnyObject], onComplete:[String:AnyObject]->()) {
var requiredData:[String:AnyObject] = [:]
Alamofire.request(.GET,API.getProfile,parameters: parameters).validate().responseJSON { (response) in
if let responseData = response.result.value {
if let jsonData = responseData as? [String:AnyObject] {
requiredData["UserName"] = jsonData["UName"]
requiredData["UserEmail"] = jsonData["UEmail"]
requiredData["UserMobileNo"] = jsonData["UPhone"]
requiredData["UserAddress"] = jsonData["UAddress"]
requiredData["UserCity"] = jsonData["UCity"]
}// Inner If
} // Main if
onComplete(requiredData)
}// Alamofire Closed
}// Func closed
Network Call within required VC :-
override func viewDidLoad() {
super.viewDidLoad()
let parameters:[String:AnyObject] = [
"WebKey": API.WebKey.value.rawValue,
"UId":NSUserDefaults.standardUserDefaults().integerForKey("UserId")
]
NetworkInterface.getProfile(parameters) { (responseDictionary) in
//print("Passed Data \(responseDictionary["UserName"])")
self.userData = responseDictionary
self.updateUI()
}
}
As far as i know, VC Lifecycle is somewhat as follows :-
init(coder aDecoder:NSCoder) -> viewDidLoad -> viewWillAppear -> viewWillDisappear
However, Even after view appears it takes few seconds for user Information to be displayed in those textfields. I thought viewDidLoad is the best place to make network calls.
I understand that network calls are async so it will take time to fetch required data from network and respond. However, network call was made in viewDidLoad so by the time view will appear, it should already have required data ? Should it not ?
So can anyone explain me which is the best place to make network calls and why? I want textfields to be updated with user Info as soon as view Appears.

Requests need to be fired in the viewWillAppear:, only this method notifies you that the screen is about to be shown. If you don't want to send requests every time the screen is shown, consider caching the data once you have it.
viewDidLoad is not the best candidate. It has nothing to do with the appearance of the screen. It's called right after a view controller's view is requested for the first time, not when the screen is showing up.
For example, if the screen was destroyed (by popping from a navigation controller), you'll receive viewDidLoad when you show it again (by pushing the screen to the navigation controller). Or if the app receives a memory warning, a current view is unloaded and loaded again, which ends up sending the view controller viewDidLoad.
viewDidLoad is tricky.
If you think that viewDidLoad will save you from fetching the data from the server multiple times: sometimes it will, sometimes it won't. Anyway, it's not the right tool to optimize networking, caching is!
Since remote requests are expensive (they take time and traffic), you want to understand when are they sent. viewWillAppear: gives you understanding. And in conjunction with caching you can make it optimal.
UPDATE
In most cases, it's not a good idea to send requests from the view controller directly. I would suggest creating a separate networking layer.

I think viewDidLoad is the correct place to make the network call if it fits that screen's need. i.e. you don't have to re-request the data at some point. For example if profile data has changed since the view was loaded.
As for network requests taking time, it's possible that your view appears before the network request is done. I suggest adding some loading indicator that you hide after the request completed.
Also, keep in mind that network requests can fail so you should deal with that by retrying the request or displaying an error message.

Related

iOS - When global calculation happens UIViewControllers

class HealthViewController: UIViewController {
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
var fruits: [Fruit];
override func viewDidLoad() {
super.viewDidLoad()
self.fruits = FoodUtils.getFruitList() // some expensive operations
}
}
I wonder for above class in iOS/Swift,
When FoodUtils.getFoodList() is prepared on runtime?
What is the good practice? preparing list inside viewDidLoad or in class scope? Which lifecycle of UIViewController will effect the memory on runtime for both cases?
In the code (object initialization):
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
the expensive operations are performed when the view controller instance is created.
in the code (inside the viewDidLoad):
self.fruits = FoodUtils.getFruitList() // some expensive operations
the expensive operations are performed once the interface elements (IB outlets) have been hooked with the viewcontroller, and the views have been loaded.
In practice it doesn't make a difference because viewDidLoad is performed after the class has been initialized WHEN USING SEGUES (for programmatically shown VCs read the note at the end).
If you are talking about an operation that can take several seconds, then the best practice would be, to perform the expensive operations BEFORE showing the view controller while a busy view (a view with an activity indicator) is shown.
Alternatively, you could do it in the viewDidAppear method, and start the View controller with an activity indicator shown, then when the expensive operations finishes, hide the activity indicator and load your data.
As a matter of fact, the second approach is used very commonly, specially when showing big lists of data. You must have seen it when using apps that start with a spinning indicator until the data is ready to be displayed.
Note:
You can separate the timing of the 2 functions if you are showing your view controller programmatically, since the first one is performed when you use the "load from nib" method. While the second one is performed once you actually try to access any views inside it.
Note 2:
Expensive network operations should always be performed on background threads so that the UI is not blocked. Which is why people often show activity indicators while info is being retrieved in the background.

Checking the current view state after block/closure completion

Within a asynchronously executed block/closure, I want to get a check on my current state before I executed anything within that block.
A common example of where this presents itself is segueing to the next View Controller after a NSURLsession request.
Here's an example:
#IBAction func tappedButton(sender: UIButton) {
//This closure named fetchHistorical goes to the internet and fetches an array
//The response is then sent to the next view controller along with a segue
Order.fetchHistorical(orderID, completionHandler: { (resultEnum) -> () in
switch resultEnum {
case .Success(let result):
let orderItemsArray = result.orderItems!.allObjects
self.performSegueWithIdentifier("showExchanges", sender: orderItemsArray)
default:
let _ = errorModal(title: "Error", message: "Failed!")
}
})
}
Assume that the user has been impatient and tapped this button 3 times.
That would mean this function will be called three times and each time it would attempt to segue to the next view controller (iOS nicely blocks this issue with "Warning: Attempt to present on whose view is not in the window hierarchy!")
I wanted to know how do you folks tackle this problem? Is it something like ... within the closure, check if you are still in the present viewcontroller ... if you are, then segueing is valid. If not, you probably have already segued and don't execute the segue again.
***More generally, how are you checking the current state within the closure because the closure is executed asynchronously?
Since the closure isn't executing on the main thread the state would be in accurate if you check it here (as you stated). You can use GCD to go to the main thread and check the state there. There are a couple of ways you can keep this code from running multiple times. If it will take some time to perform the calculations you can use an acitivity indicator to let the user know the app is busy at the moment. If you want the user to still have the option of pressing the button you can put a tag like:
var buttonWasTapped:Bool = false //class property
#IBAction func tappedButton(sender: UIButton) {
if !self.buttonWasTapped{
self.buttonWasTapped = true
}
}
Then change it back to false on viewDidAppear so they can press once every time that page is shown.
When starting some task that will take some time to complete I would do two things;
Show some sort of activity indicator so that the user knows something is happening
Disable the button so that there is further indication that the request has been received and to prevent the user from tapping multiple times.
It is important that you consider not only the correct operation of your app but also providing a good user experience.

Long delays displaying UIImageView loaded from local file?

I see questions regarding long delays in displaying UIImageViews after downloading, but my question involves long delays when
reading from local storage.
After archiving my hierarchy of UIImageViews to a local file (as per narohi's answer in
How to output a view hierarchy & contents to file? ),
I find that if I want to reload them, it takes 5 to 20 seconds for the views to actually appear on screen,
despite my setting setNeedsDiplay() on the main view and all the subviews.
I can immediately query the data contained in the
custom subclasses of UIView that get loaded -- showing that NSKeyedUnarchiver and all the NS-decoding and all the init()'s have completed -- however
the images just don't appear on the screen for a long time. Surely the next redraw cycle is shorter than 5-20 seconds...?
It seems odd that images from PhotoLibrary appear instantly, but anything loaded from local file storage using NSKeyedUnarchiver takes "forever."
What's going on here, and how can I speed this up?
.
.
To be explicit, the relevant part of my Swift code looks like this:
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
myMainView.addSubview(view)
view.setNeedsDisplay()
// now do things with the data in view ...which all works fine
I find that, even if I add something like...
for subview in view.subviews {
subview.setNeedsDisplay()
}
...it doesn't speed up the operations.
We are not talking huge datasets either, it could be just a single imageview that's being reloaded.
Now, I do also notice these delays occurring when downloading from the internet using a downloader like the one shown in
https://stackoverflow.com/a/28221670/4259243
...but I have the downloader print a completion message after not only the download but when the (synchronous operation)
data.writeToFile() is complete (and before I try to load it using NSKeyedUnarchiver), so this indicates that the delay
in UIImageView redraws is NOT because the download is still commencing....and like I say, you can query the properties of the data and it's all in memory, just not displaying on the screen.
UPDATE: As per comments, I have enclosed the needsDisplay code in dispatch_async as per Leo Dabus's advice, and done some Time Profiling as per Paulw11's. Link to Time Profiling results is here: https://i.imgur.com/sa5qfRM.png I stopped the profiling immediately after the image appeared on the screen at around 1:00, but it was actually 'loaded' during the bump around 20s. During that period it seems like nothing's happening...? The code is literally just waiting around for a while?
Just to be clear how I'm implementing the dispatch_async, see here:
func addViewToMainView(path: String) {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
dispatch_async(dispatch_get_main_queue(), {
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
...Since posting this I've found a few posts where people are complaining about how slow NSKeyedUnarchiver is. Could it just be that? If so, :-(.
SECOND UPDATE: Ahh, the "let view = " needs to be in the dispatch_async. In fact, if you just put the whole thing in the dispatch_async, it works beautifully! so...
func addViewToMainView(path: String) {
dispatch_async(dispatch_get_main_queue(), {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
This works instantly. Wow.. Credit to Leo Dabus. Leaving this here for others...

How to run a function when an app first loads

I'm trying to write a simple to-do list in Swift that will store the list as an array of Strings and then call it back from memory when the app loads.
I've got the following code:
var itemList = [String]()
func loadData() -> [String] {
var arr = [String]()
if NSUserDefaults.standardUserDefaults().objectForKey("storedData") != nil {
arr = NSUserDefaults.standardUserDefaults().objectForKey("storedData")! as! [String]
}
else {
arr = ["Nothing to do..."]
}
return arr
}
func saveData(arr: [String]) {
NSUserDefaults.standardUserDefaults().setObject(arr, forKey: "storedData")
}
Where I'm getting stuck is in where to place the call to loadData(). This is an app that has two view controllers (one for the list, one for an add item setup), so if I place the loadData() call in viewDidLoad() for the main view controller, the array is called back in from memory (and over-written) every time I switch back to the main view controller.
Where is the best place to call this so that it will load once only, upon the app starting up?
the array is called back in from memory (and over-written) every time I switch back to the main view controller.
No. viewDidLoad only loads once, when the app starts. Only viewWillApprear and viewDidAppear get called everytime the viewcontroller changes.
Also you could make your code a bit more compact by using if let:
if let storedData = NSUserDefaults.standardUserDefaults().objectForKey("storedData") as! [String]{
arr = storedData
}
But if you want to make sure to load this only once, you can put it in your AppDelegate file in your applicationDidFinishWithOptions method.
But you'd have to make a variable in your AppDelegate file which you can access from your viewController.
viewDidLoad() only happens when the View Controller is first instantiated. If it is your root view controller you can have it in viewDidLoad().
The other goes, viewDidLoad > viewWillAppear > viewDidAppear. After the view is first loaded only the latter 2 methods are called whenever you navigate.
you can also always register for a NSApplicationDidFinishLaunchingNotification notification at the notification center
check it out here:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/c/data/NSApplicationDidFinishLaunchingNotification
Use or overwrite respectively
application(_:didFinishLaunchingWithOptions:)
of your application delegate.
That is called only once upon application launch.
See
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didFinishLaunchingWithOptions:
viewDidLoad is called after the view for a single controller is first loaded. It shouldn't be called more than once for the life-cycle of a single viewController. Maybe it is possible that, if you are not calling "super.viewDidLoad()" in your own viewDidLoad method, then it may be called again? While you can generally assume that the rootViewController for an application is only created once, I think it's theoretically possible that it might be cleared out of memory by the app if required and then recreated again - so I would never assume it's only called once.
One thing you could do is just set a boolean (default false) to true whenever you load the data and then not call it again if the flag is already set to true.
Alternatively, it's a good idea to separate the data management from your viewControllers. A relatively simple solution would be to have a class called "AppData" say, which might be a singleton (so you can only ever have one instance of it) or a member of your AppDelegate. Then, in your app delegate's "applicationDidFinishLaunchingWithOptions method, you could create the one instance of the AppData class and call the loadData method on it. This class would then live independently of whichever view is currently showing, and the current view could call methods on this object to load/save/update data as required.

Simultaneous NSURLConnection from within prepareForSegue

I have a UITableView that requests for more data from the server when the user hits the bottom of the table (similar to the Twitter application). However, I'm trying to use a modal segue to filter out data to the user's desire. In order to properly select which data to filter, I have to load ALL of the data to categorize it. In order to load everything, I am required to send out multiple NSURLConnections to load multiple pages. I am trying to have it so when one completes, the next one starts.
However since the connection completes with connectionDidFinishLoading, I have not figured out a way to send out simultaneous NSURLConnections from within prepareForSegue. I tried using a while loop in prepareForSegue as follows:
while (All of the data is not loaded) {
if (isLoading == NO) {
[self loadMoreResults];
}
}
where "isLoading" is a BOOL declared in my viewcontroller's implementation file. isLoading changes value to YES inside loadMoreResults, and changes back to NO at the end of connectionDidFinishLoading. However, within prepareForSegue, isLoading never changes back from YES to NO.
Is this a multithreading issue? I have done research on other questions and see that NSURLConnection has a class method sendAsynchronousRequest:queue:completionHandler: where the completion handler might help, but I'm unsure how I would use it.
ALSO: I want to continue executing prepareForSegue AFTER the last connection finishes, not right after it sends the request.
Thanks in advance!
Then you should not link the segue directly from the bar button item to the view controller in the storyboard.
Just link a general segue with identifier from the table view controller to the filter view controller.
And from the bar button item, create an action for it from the storyboard so that you can send out multiple NSURLConnections first.
Finally then in your code, after the last connection finishes, call the performSegueWithIdentifier method.

Resources