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.
Related
RxSwft is very suitable for iOS MVVM.
Putting viewmodel everywhere, disobeys Law of Demeter ( The Least Knowledge Principle ).
What is the other drawbacks?
Will it leads to Memory Leakage?
Here is an example:
ViewController has a viewModel
ViewModel has some event signals, like the following back event
class ViewModel{
let backSubject = PublishSubject<String>()
}
ViewController has contentView and viewModel, and contentView init with viewModel
lazy var contentView: ContentView = {
let view = ContentView(viewModel)
view.backgroundColor = .clear
return view
}()
and ViewModel's various subject are subscribed in viewController to handle other part view
viewController is a Dispatch center.
ViewModel is Event Transfer station. ViewModel is in everywhere, in Controller, in View, to collect different event triggers.
the code is quite spaghetti
in ContentView, user tap rx event , binds to the viewModel in viewController
tapAction.bind(to: viewModel.backSubject).disposed(by: rx.disposeBag)
user events wires up easily.
But there is memory leakage actually.
So what's the other disadvantages?
ViewModel doesn't break the Law of Demeter but it does break the Single Responsibility Principle. The way you solve that is to use multiple view models, one for each feature, instead of a single view model for the entire screen. This will make view models more reusable and composable.
If you setup your view model as a single function that takes a number of Observables as input and returns a single Observable, you will also remove any possibility of a memory leak.
For example:
func textFieldsFilled(fields: [Observable<String?>]) -> Observable<Bool> {
Observable.combineLatest(fields)
.map { $0.allSatisfy { !($0 ?? "").isEmpty } }
}
You can attach the above to any scene where you want to enable a button based on whether all the text fields have been filled out.
You satisfy the SRP and since object allocation is handled automatically, there's no concern that the above will leak memory.
You are right, there are some drawbacks, if you want just a data-binding, I would suggest to use Combine instead, since no 3rd party libraries need, you have it already. RxSwift is a very powerful tool when you use it as a part of language, not just for data binding.
Some of suggestions from my experience working with RxSwift:
Try to make VMs as a structs, not classes.
Avoid having DisposeBag in your VM, rather make VC subscribe to everything(much better for avoiding memory leaks).
Make its own VMs for subview, cells, child VC, and not shared ones.
Since your VC is a dispatch centre, I would make a separate VM for your content view and make a communication between ContentView VM and ViewController VM through your controller.
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.
I've extracted my NSFetchedResultsController's into a separate object. I'd like to monitor when the view controller appears and disappears so that I can pause and resume the FRC delegate methods to update the tableview with new content. Is this possible without any responsibility from the view controller itself? I.e. I know I could use delegates or notifications, but I am looking for a solution where I don't have to sprinkle code all over the view controllers.
It seems there isn't an official way to do this, so here's what I did.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.fetchedController willAppear];
}
And then handled the necessary logic in there... pretty basic.
Maybe another time I'll post about my fetchedController. It's pretty neat: it holds a UISearchController (and delegates), 2 data sources (one for the regular view, and one for the search). There's a protocol that the view controller implements (tableView, entity name, context, sort descriptors, configureCell, etc) so I never have to create search controllers, NSFetchedResultsController's, or any of the delegates directly. It's much cleaner than having a god UIViewController superclass.
I've got a UI with several elements, some of which I'd like to encapsulate into their own objects. For example, one object would be a timer (basically just a UILabel sitting in a UIView) with the externally-available members:
startTime (property)
start and pause (methods)
(And also a im_done NSNotification when the timer reaches 0.) This object and several others would be used together in a single UIViewController.
Would a UIView or UIViewController be more appropriate to subclass for the timer object (and all the others)? Reading this answer leads me to believe UIView, but for my specific case (especially for the more complicated objects), I'm not sure. Thanks for reading.
EDIT: Just to clarify, I would want all code that implements timer functionality separate from my main view controller. One big reason is so that the timer object would be portable.
You should have a subclass of UIView, but it should just have the view components to display whatever time information you need. You should also have a MyTimer class which encapsulates the timing functionality and provides callbacks on changes (such as the time remaining and perhaps the completion as a separate when the time remaining reaches zero). Ideally the callbacks pass the MyTimer and, as a convenience, the remaining time value so you don't need to use the accessor method in the simple use case).
Now, you already have a view controller which is managing your broader view, and it can create add your UIView subclass to the main view and create, own and manage the associated MyTimer instances which drive those views.
This I think comes down to preference. You'll include a uiview and uiviewcontroller in a parent view controller differently and this difference can make a uiviewcontroller more difficult if you don't understand containers. I would say best practice would be in a uiviewcontroller but it really is up to you.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.