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.
Related
I learned a very painful lesson about how to ensure your outlets are nil on a view controller's viewDidLoad, and that's by writing an initWith... method of your own.
-(id)initWithDoohickey:(SomeDoohickey*)doohickey
{
self = [super init];
if (self)
{
theDoohickey = doohickey;
}
return nil;
}
Now when you call this method, instead of initWithNibName, or simply init, your view outlets will be nil, and you'll run into all kinds of debugging nightmares.
So I see two solutions here...
(1) Call init or initWithNibName and let it do it's thing, and follow that with a classInQuestion configureWithDoohickey:(SomeDoohickey*)doohickey
or (2)
figure out how to properly write an initWith... method that actually loads the nib properly, while also allowing me to pass in whatever config variables I want in the same call.
I'd like to know how to do (2).
Simply replace your call to [super init]; with a call to [super initWithNibName:...];.
Obviously you need to pass in the correct arguments.
The problem you have is that you didn't properly chain the initializers. A subclass's initialization process must (eventually) call its superclass's designated initializer. The designated initializer ensures that all of a class's data is correctly set up.
If you don't call the superclass's DI, it's not able to set the properties that were defined in the superclass.
The simple init that you're using is not UIViewController's designated intializer; initWithNibName:bundle: is. As rmaddy said, that is the method you should use in your subclass's own designated intializer.
Initializers in the subclass can use each other, as long as one of them eventually passes up to the superclass's DI.
(As an aside, this relationship has been formalized in Swift. Initializer chaining is actually part of the language, rather than just a convention. This can be confusing, but interestingly, the way you wrote initWithDoohickey: would not have even compiled in Swift.)
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.
My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks
Is there an easy way to know when all the subivews have loaded?
Right now I'm doing:
if([[self subviews] count] == 10) {
//do stuff
}
If there isn't an event/method for this, is there at least a way to dynamically know what the child count is GOING to be?
edit
I re-read this just now and realize it's a bit asinine. Let me clarify:
I'm loading this UIView from a XIB file and I wanted to know when the NIB has officially loaded (with all of it's children). So I dare say the correct answer would be awakeFromNib
If you're calling this from a viewController, just use
-(void)viewDidLoad;
Which is called after the view and all its subviews are loaded. If you're doing it from one of the views inside the nib, use:
-(void)awakeFromNib;
Which is called after the view's subviews have been loaded.
If you are adding subviews programmatically (e.g. [myView addSubview:anotherView]), then of course there is no way to know; the program could add more subviews at any time, if you write it that way.
If you are loading the view from a nib, you are probably looking for the awakeFromNib method. From the NSObject UIKit Additions Reference:
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.
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.