The [UIViewController viewDidLoad] method is called by the system after loading the associated view (obviously?). A common belief -- which I share -- is that viewDidLoad should not be called directly †. However, I can't find this guidance in the documentation, nor anything else from Apple. Does it exist?
For comparison, the loadView documentation says
You should never call this method directly.
† Excepting [super viewDidLoad] in an overridden method.
I don't think there's any rule that forbids it. I don't mean "there's a rule we all know, it's just not written down." I mean "I don't think there's any rule that forbids it." You are, as best I am aware, free to call viewDidLoad whenever appropriate (including its super).
That doesn't mean you should call it. But then you almost certainly should never call OSCompareAndSwap either. In both cases, if you had a good reason and knew what you were doing, it could be appropriate to call. But it's unlikely to come up.
While I can't think of a time I've had to call viewDidLoad directly, I have had to manually call viewWillAppear and viewDidDisappear to manage view lifecycle in a custom container view controller. There's nothing that forbids calling view lifecycle methods if that's what you mean.
That said, it would be bad practice to call a view lifecycle method if you didn't mean "the view has transitioned into this state." And since it's pretty hard to get into a situation where you have loaded the view, but viewDidLoad won't be called, it's hard to imagine many cases where it would be useful. And you shouldn't call a method uselessly. So that's the only rule at play here as far as I'm aware.
It is not mentioned explicitly but if you would call it, you would somehow violate the "contract" of the method specified in its documentation:
Called after the controller's view is loaded into memory.
Related
I am wondering if it is a good practice to implement a deinit on every view controller to check if it is correctly removed when it disappears and avoiding leaking memory?
By default, you don't have to implement the deinit method in your classes:
Swift automatically deallocates your instances when they are no longer
needed, to free up resources. Swift handles the memory management of
instances through automatic reference counting (ARC), as described in
Automatic Reference Counting. Typically you don’t need to perform
manual cleanup when your instances are deallocated. However, when you
are working with your own resources, you might need to perform some
additional cleanup yourself. For example, if you create a custom class
to open a file and write some data to it, you might need to close the
file before the class instance is deallocated.
Swift Deinitialization Documentation - How Deinitialization Works Section.
Usually, when working with View Controllers it seems that there is no need to do such an implementation. However, as mentioned in #rmaddy's comment, it is still an approach for tracing memory leak or reference cycle with the view controller.
If your purpose is to check if the controller has been removed from the hierarchy (view controller life cycle), you could implement viewWillDisappear(_:) or viewDidDisappear(_:) methods; Note that calling on of these methods does not guarantees that the deinit will be called, i.e it does not mean that disappearing the view controller always leads to deallocate it (related: Deinit never called, explanation for deinit not called).
Also:
these Q&As should be useful:
When should I use deinit?
how to make deinit take effect in swift
Understand deinitialization and inheritance in swift language
Swift automatically deallocates your instances when they are no longer needed, to free up resources. So to add deinit on all your viewControllers seems unnecessary. You should call deinit whenever you need to do some action or cleanup before deallocating an object.
Well during the tests phase it maybe good idea, because you can check if everything is good (eg. if you have a lot of completion handler) but overall it is unnecessary.
In methodA of my view controller VC1 another view controller's (VC2) method methodB is called in which VC1 is deallocated. Then control returns to VC1 methodA which obviously crashes when self is used.
It is not obvious that the instance is deallocated, so developers may use self without knowing that they should not. From that perspective I'd like to fix the deallocation. However, I'd like to get some opinions whether or not such a situation is common or needs to be fixed ASAP a.s.o.
Q1: Is execution of deallocated object's methods somewhat common which one will encounter in typical medium projects?
Q2: Would it be acceptable, if comments are added, which warn the developer?
Q3: Are there any other recommendations / opinions?
The project is of medium complexity (about 200 classes of which 50 are view controllers). I'd like to get a feeling about how much effort I should invest to take care of such "deallocated method calls". If it would be one of my smaller pet projects, it would be rather easy to fix anything. However, with an inherited project which already went through a couple of hands, things are not so trivial any more.
EDIT:
didFinish delegate scenarios:
Thinking about it, I came across didFinish kind of delegate calls. Assume a master view controller (MasterVC) uses a slave view controller (SlaveVC) to do some work, keeps a strong reference to it and sets itself as a delegate for the SlaveVC. When the slave is finished, it calls slaveVcDidFinish. In MasterVC's implementation of slaveVcDidFinish the strong reference to the SlaveVC is set to nil. So when the slaveVcDidFinish returns, it is SlaveVC's responsibility to never use self, as it may have already been deallocated.
- (void) notifyDelegate
{
[self.delegate slaveVcDidFinish];
// From here on, `self` may be invalid...
}
This likely is relevant, when the SlaveVC is dismissed before the call to slaveVcDidFinish, as otherwise it cannot be deallocated because it is part of the view controller hierarchy.
Q4: Is my understanding of this didFinish scenario correct?
[/EDIT]
A few details, in case it is relevant:
VC2 presents VC1 and keeps a strong reference
VC1 does its work and needs to present VC3 for which it dismisses itself before
VC3 does its work and delegates back to VC1's methodA
VC1's methodA delegates to VC2's methodB
methodB releases the strong reference to VC1, VC1 now gets deallocated (dealloc is called), and control returns to methodA
methodA does a little more work and then returns
One way to do the little more work was to call methods on an object passed into methodA as parameter. That should work, as self does not play a role there.
Another way to do that work was to call a method of VC1 using self which obviously causes a crash. So as long as one does not use self, everything should be fine.
Yes, you should fix it, but based on what you are saying your app architecture must be to a greater or lesser extent wrong. You are trying to implement an algorithm distributed across view controllers that really needs an object that lives across the lifetime of the objects you are dealing with. Such needs a mediator object.
Actually I suspect you may not need to be implementing that algorithm at all and another approach that works better with how controllers are instantiated and how control gets passed around the app would work fine, but you haven't posted sufficient info for me to be able to advise on that.
However having said that, implementing an overarching controller could certainly be used to solve your problem. A good way to do this is often to subclass your current root view controller (or better still a subclass that implements a category you define). So for example, if it is a UINavigationController you are using, create UINavigationController subclass, change the class of it's representation in your Storyboard (assuming you are using storyboards) and do the work of coordinating display and dismissal of other view controllers in there.
Also check out the Mediator design pattern http://cocoapatterns.com/ios-view-controller-transitions-mediator-pattern/
As a note, generally these days you can avoid having any object properties for strongly holding view controllers. Usually the root view controller holds strong references to any view controller it needs to present, lazy invocation should be used to present view controllers that are not currently presented or stored in an existing navigation controller stack (which keeps the memory utilisation profile in good shape) and any other child view controllers can be added in to your view controller heirarchy using the addChildViewController: method in which case they are strongly held in the childViewControllers array property.
You may want a convenient name for a view controller, but actually I would recommend writing a small bit of code for as the implementation of any such property that will identify the controller you need from amongst those in the Apple supplied properties, and dynamically retrieve it. This may seem like hard work, but it's worth it and actually, paradoxically, decreases code complexity and helps ensure you stay working with the view controllers the way Apple intended. Doing anything else increases pain.
Using separate object properties for holding references to view controllers will usually only duplicate mechanisms the existing Apple classes already provide you with. Such unnecessary duplication increases code complexity and can introduce bugs and memory management issues (of the kind you are in fact describing).
Sorry this answer is quite general, but you are seeking an answer to the wrong question. From what you have said it's clear that at some level you need to address your app architecture.
I agree the architecture seems flawed but as you say it is inherited code and not trivially small maybe some compromises must be made.
I am thinking since self (or any instance variables or properties) cannot be called anyway perhaps you could make the methods you need to call class methods (+) rather than instance methods (-). Class methods are obviously safe to call without an object instance.
I placed my code for iAd/AdMob ads in...
-(void)viewWillAppear:(BOOL)animated{}
Ads work perfectly fine the way I have them now on all iOS devices.
When I connected my iPhone to Xcode and clicked on Product -->Analyze a message states...
The viewWillAppear:instance method in UIViewController subclass 'iPhoneSIX' is missing a [super viewWillAppear:] call
I just accidentally stumbled upon this Product-->Analyze thing. Do I really need to add [super viewWillAppear] even though everything works perfectly fine on all devices as it currently is. Will Apple reject my app if I don't pay attention to the Product-->Analyze issue navigator?
Also, what does ...
[super viewWillAppear:YES];
What does calling this do?
According to Apple: (emphasis mine)
This method is called before the receiver's view is about to be
added to a view hierarchy and before any animations are configured for
showing the view. You can override this method to perform custom tasks
associated with displaying the view. For example, you might use this
method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your
implementation.
Apple doesn't gets that specific when deciding to Accept or Reject your app. It only follows the guidelines, which doesn't get that much into the weeds of your specific methods.
Calling [super viewWillAppear:YES] is a best practice, and I would recommend it. Always including super ensures that any code in the super classes get called before executing any additional code. So if you or someone else coded a super class that expected some code to be executed, you are guaranteed to still execute it, rather than just overwriting the whole method in the subclass.
Say you have a view controller of type MyViewController which is a subclass of UIViewController. Then say you have another view controller of type MyOtherViewController, which is a subclass of MyViewController. Say you're coding now some things in viewWillAppear in MyOtherViewController. If you call super first, it will call viewWillAppear in MyViewController before executing any code. If viewWillAppear in MyViewController calls super first, then it will call viewWillAppear in UIViewController before executing any code.
I'm quite certain Apple will not reject your app for failing to call super on an overridden method, primarily because there are cases where you may specifically want to avoid calling super.
That said, as Josh Gafni mentions it is definitely a best practice to do so, unless you have a very good reason for not. Also bear in mind some view controller subclasses (can't recall specifically which ones, but maybe UICollectionViewController) will only work properly if their view lifecycle methods get called appropriately, so not calling super can definitely break some classes (sometimes in subtle ways you may not realize).
Therefore my suggestion is add the call to super (generally as the first line in the method) and see if things continue to work fine. If not, spend a bit of time trying to understand what is happening differently and see if you can solve it in a different way. In general you should always (as a force of habit) provide calls to super on any view lifecycle methods you override whenever possible.
I am really struggling in understanding what to do in didReceiveMemoryWarning. From what I read on StackOverflow and blogs, the following is my understanding -
Generate all data that your view needs in viewDidAppear and destroy those (set to nil) in didReceiveMemoryWarning. This sounds good to me because those properties can be recreated in viewDidAppear.
However, the problem is that didReceiveMemoryWarning is also called for the view which is currently visible. In this case, obviously I would not deallocate data for the view. Shouldn't didReceiveMemoryWarning NOT be called for the view which is currently visible. But that is not case - How can one handle this?
The Idea with didReceiveMemoryWarning is that any Data that you have that can be re-created, should be deallocated and created from that point on whenever the user needs it. Traditionally, this doesn't include UI components.
So unless you have a ton of UIControls on your screen, it's probably not worth it to write the code to re-cycle, or recreate them (especially since this is done for you in UICollection's and UITableView's). That said, if you're getting a didReceiveMemoryWarning, it's probably because you're keeping some stuff in memory around that you don't need.
Actually this is a simple question with maybe not an answer for a central solution.
I would like a way to monitor in a central way every UIView start-finish loading to get metrics of the application.
I can see that viewWillLoad doesn't exist anyway in the UIViewController class and viewWillAppear is not something that it could serve the purpose.
Is this feasible in any way? I'm thinking of searching every UIView inherited class in the application and inject code somehow, but as I said I will need two methods.
Or maybe inject code to a protocol that already exists in the UIView class?
Any thoughts?
Regards.
If you are only concerned with the loading of the root view of every view controller. You could swizzle -loadView and -viewDidLoad of UIViewController. You could exchange those with methods that add start and end times to a table for each instance that calls them. Keep in mind though, that just the act of measuring it, you are going to slow down the loading.
If you want to track the loading of every view, you could swizzle -initWithFrame on UIView in the same way. In your version of the method you would start tracking before you call the real version and stop tracking once the real method has exited. However, if you have custom views that have their own initializers you will have to come up with a more complex version of that. However I do not recommend doing this. The time it takes to initialize a view is probably less time or at least very close to the amount of time it will take to track it making the timings worthless.