I am converting an iPhone app to a Universal app. I have a NIB view which I want to use on the iPad as is but resized and positioned. On the iPhone I am initializing normally with initWithNibName...
EventEditViewController *eventEditViewController = [[EventEditViewController alloc] initWithNibName:#"EventEditViewController" bundle:nil];
I found that this did not work well for me on the iPad for various reasons. So I created my own initialization method to call instead when running on the iPad...
EventEditViewController *eventEditViewControllerForIPad = [[EventEditViewController alloc] initWithFrame:iPadFrame eventDate:longDate event:eventName delegate:self];
This solved a couple of problems. One how to resize and position the view where I wanted it and how to properly initialize certain variables. I am actually passing more variables than you see here.
It works really well, but I just now noticed that, unlike initWithNibName viewDidLoad fires before my initWithFrame method. I only found this out because a variable I was trying to access in viewDidLoad was showing up as a zombie and I thought I was initializing it in my initWithFrame method.
I was surprised by this behavior. Is it normal? It doesn't make sense to me that the view would be loaded before the named initMethod in the alloc/init call.
I am now wondering if what I am doing might not be a good thing. Like I said it works really well, but should I not use my own initialization method here?
If it's ok to do it this way, maybe someone can explain why the view loads before the init method.
Thanks,
John
If your -initWithFrame:... method is accessing the view controller's view property, -viewDidLoad will be called before the init method completes because the view accessor will cause the view to be loaded.
As for whether it's okay to use your own method, it should be fine provided that your init method calls the designated initializer for the class.
Initializing member variables should be done in viewDidLoad or awakeFromNib.
awakeFromNib is the first method that gets called when a view comes to life from a Xib.
It's preferred to use viewDidLoad for allocating memory for huge arrays since you can deallocate them in viewDidUnload.
Both navigation controller and tab bar controller uses view loading methods to unload views when other views demand more memory.
Allocating in anyother methods should be avoided as far as possible.
From Apple's PageControl source code
// load the view nib and initialize the pageNumber ivar
- (id)initWithPageNumber:(int)page
{
if (self = [super initWithNibName:#"MyView" bundle:nil])
{
pageNumber = page;
}
return self;
}
You can have your own custom init method defined in that EventEditViewController and you can use a custom method like above to initialize your viewController and set as many member variables as you want like iPadFrame, longDate, eventName etc in your case.
Just make sure you call it exactly as above as it's important to call super implementation in such custom init methods.
Also just to shed more light on where you should release arrays you created in viewDidLoad method, it's the dealloc method first in addition to viewDidUnload. The reason behind this is viewDidUnload method doesn't always get called. It gets called only when application starts receiving memory warnings. As compared to this, the method dealloc gets automatically called always when you release the initialized viewController and it's retain count reaches 0. You should release the arrays you initialized viewDidLoad method and your other retain properties in dealloc method.
Also keep in mind that when the app receives memory warning, it's actually a chance to free up additional memory. Also the viewDidUnload method gets called for all the viewControllers in memory except visible one at that time.
Related
I am using a UITabBarController, and my 3rd tab observes an array on a singleton data store (implemented in viewDidLoad).
Currently if I just log out (and change root view controller from App Delegate), the app will crash when dealloc is called on that 3rd tab with the message "cannot remove observer for the key path "X" because it is not registered as an observer.
Using breakpoints, I see that viewDidLoad is never called on this 3rd tab, however dealloc is being called when I sign out. What is going on? I assume the UITabBarController is holding a reference to the 3rd tab when I enter the storyboard, but does not "load" that tab. Yet iOS calls dealloc on it when I release the tab bar controller.
Should I use a boolean to track viewDidLoad execution, or try to remove the observer with a #try statement? Is there an overall better design for this?
Do not use #try. Exceptions in Objective-C should always be considered programmer error, and should be fatal.
As you say, use a boolean ivar set in -viewDidLoad to avoid this.
The view has not been loaded because views are only loaded when they are required for display.
Raw KVO can be dangerous and unwieldy. While not required to answer this question, ReactiveCocoa significantly improves the KVO experience.
viewDidLoad is called before the view appears for the first time. UITabBarController is creating the relevant UIViewController, but the view is not loaded during creation. It is loaded on-demand, when a user visits the tab for the first time.
KVO removal is problematic, I don't think you can avoid using #try in dealloc. I would suggest to use KVOController: it's fairly easy to use and it would also handle all the edge cases for you.
May have found an even better solution. I add the observer in the method initWithCoder:(NSCoder *)aDecoder, which is called when the parent UITabController is loaded. I am using the storyboard which may be why I need to call override this method instead of regular init. Doing this now without the need for a BOOL flag or #try and no crashing.
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[anObject addObserver:self forKeyPath:aKeyPath options:0 context:NULL];
}
return self;
}
Use a flag to set whether or not KVO has been set up. Using #try can create memory management issues depending on the state of the app.
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.
My initial view controller is loaded, and I need an NSArray to be init'd, should I take care of this in an awakeFromNib method or an initWithCoder: method? awakeFromNib seems to work nicer, as I don't need to return anything, but it works as nib files were what used to be used right? I don't want to use a method that will break soon.
And would initWithCoder: just look like:
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
self.articles = [[NSMutableArray alloc] init];
}
return self;
}
The point of -awakeFromNib is so that you can do init stuff when you can be sure that all your connections to other objects in the nib have been established.
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
Don't forget to call super.
It is unlikely to go away any time soon, and if it did so much code uses it that the transition period would be long. Yes its name comes from the old "nib" file format but this stack overflow question clears up the differences in the file extensions.
So in summary either method will work for you as you are setting an internal instance variable for the class. Note that inside init methods (including -initWithCoder) it may not be safe to use your setter methods in case setters rely on the class being fully initialised (source WWDC 2012 video moving to modern objective-c). An example would be setting a property that references another object in the nib file.
In UIViewController subclasses -initWithCoder is only called when loading from a storyboard. As -awakeFromNib is called whether you use storyboards or not it might make more sense to use that.
Another pattern you could consider is the lazy-getter:
- (NSMutableArray *)articles{
if (_articles){
return _articles;
}
_articles = [[NSMutableArray alloc] init];
return _articles;
}
The benefit of this approach is that if you wanted to do further setup to the array you can easily discard the array when you don't need it anymore and the next time you access the property you have a fresh one again.
I have a view, which is subclass of UIWebView. It has a property called Contact which is a managed object. The view uses templating engine to create a html with the object and then load into UIWebView. I thought it would be a better idea to monitor the object in the view itself, such that whenever something changes in the object, the view refreshes automatically. So, observed for certain attributes of the managed object in the view itself. And then to avoid the notification coalesce, I have made it such that the reload is done with
[self performSelector:#selector(refresh) afterDelay:0 ].
It refresh the webview automatically whenever it finds the change but also gives some strange crash. The crash says [MyWebView retain] message sent to deallocated object. I know I have properly removed observing values in dealloc method. But, it seems like dealloc gets triggered after a while. I have a strange issue related to releasing the view. The view stays for a while, although the view controller is already released and then releases after may 2/3 seconds. It is really strange. I think the crash is because of this.
Please do suggest me any idea. I will be glad to hear your suggestion. There are something wrong certainly, if anybody could point me I would really be grateful.
Using the delegate design pattern can cause EXC_BAD_ACESS KERN_INVALID_ADDRESS crashes if not used properly. If you have processing that is running in background threads that use the delegate design pattern, where in the object you set SELF as the delegate then you must remove SELF as the delegate in the dealloc method (even under ARC) by setting the delegate reference to nil, or there is a possibility that the object will try to call back into your deallocated object using the delegate design pattern. So if you have something like this in your object.
[_xmlParser setDelegate:self];
you should always have a dealloc method even under ARC to prevent the possibility of a crash in the case where your object gets destroyed while still doing work. It is very common to have your object destroyed while doing work. imagine a UIViewController that shows images from the internet. If you had a FetchImage class that used the delegate design pattern to lookup images that then calls a routine on the object when the lookup finishes, it is easily for the user to pop into and out of your UIViewController while your FetchImage object is still doing work on the background thread. You might not ever notice this when testing, but if you have hundreds of users, some of them will notice because the app will crash when your object tries to call a method on the SELF reference.
If your object uses the delegate design pattern, always have this to cleanup:
#pragma mark - dealloc - cleanup delegate references to prevent callbacks into deallocated objects (EXC_BAD_ACCESS / KERN_INVALID_ADDRESS)
- (void)dealloc
{
[_xmlParser setDelegate:nil];
// for non ARC based code you would also call: [super dealloc];
}
search every class in your project, if you have setDelegate:self or delegate = self then your users are most likely experiencing race condition crashes with your app if you don't have a dealloc cleanup method as described above. If you don't have the dealloc, add it even if you never see crashes when testing. -rrh
I've found a case where some of my view controllers' initWithCoder methods are invoked before the application didFinishLaunching method in the application delegate. (I've confirmed this by setting breakpoints and looking at the sequence of invocations)
I'm using a storyboard. A UITabBarController is the initial view controller. Part of the problem is that the storyboard creates objects in an unknown order; perhaps it's creating the view controllers before the app is done launching.
In any case, the problem is that I'm registering initial user defaults. This must happen before any piece of the program looks at them. So, I'm trying to find the spot where the registering code will be guaranteed to execute first.
Is there any such place?
Note:
This thread discusses it a little, but there isn't really a conclusion...
ViewDidLoad runs before AppDelegate didFinishLaunchingWithOptions gets executed!
The standard means of initializing user defaults is in a "+(void)initialize" method in your app delegate:
+ (void)initialize
{
if(self == [MyAppDelegate class]) {
...
}
}
This is guaranteed to run before any delegate method gets messaged.
PS: I instantiate a whole bunch of viewControllers in my didLaunch method before returning from that method.
This is natural (and also one of the reasons why using InterfaceBuilder sucks). In application:didFinishLaunchingWithOptions: you generally rely upon the main window and main view controller having already been created from their corresponding NIB/XIB files. Two solutions:
One (preferred): instantiate stuff manually in application:didFinishLaunchingWithOptions:. You can thus control the execution order of any initialization.
Two: use __attribute__((constructor(XXX))) functions - they're guaranteed to be called before main, and the lower the XXX number the earlier the particular constructor function is called. This method is, however, not advisable, because it isn't standard C (only a compiler extension), and it also can easily get very confusing.