I've been programming in Objective-C for years now, and I always bump into this problem: If you have an object with multiple initializers, but there is code in common to all of them, how do you extract it out?
The best I've been able to come up with is something like this:
#implementation Example
- (void)privateInitWithString:(NSString*)aString
{
self.str = aString;
}
- (id)init
{
self = [super initWithWindowNibName:#"MyNib"]
if(self) {
[self privateInitWithString:#""];
}
return self;
}
- (id)initWithString:(NSString*)aString
{
self = [super initWithWindowNibName:#"MyNib"]
if(self) {
[self privateInitWithString:aString];
}
return self;
}
#end
There is a lot of duplication in the individual initializers which a code smell. However I can't think of a way to get one initializer to "fall through" to another one because there is no guarantee that self has been set before calling [super init]
Is there a best practice for this that I'm missing?
You write one "designated initialiser". That initialiser handles all the different situations. In your case, initWithString seems a good candidate. And init just becomes
- (instancetype)init { return [self initWithString:#""]; }
You can read more about it here:
https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/Initialization/Initialization.html
Related
While referring a sample code i found this snippet can any explain why it is used.
- (id)init
{
self = [super init];
if (self) {
[[self view]setBackgroundColor:[UIColor redColor]];
}
return self;
}
and what is the difference between the following snippet.
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
init and viewDidLoad both are completely different.
viewDidLoad called, when the view is loaded into memory, this method called once during the life of the view controller object. It's a great place to do any view initialization.
init method is an initializer method. Cocoa has various types of intializer. To learn more, please check the link,
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html
At first, sorry for my english. I trying to resolve problem with a few enter points (a few initializators like initWithFrame: and initWithCoder:). Not to repeat my setup code. At first i had a simple solution, just create method ("setup" for example) that called by initializators. But there is a little problem with subclasses. If my subclass have own initializator like initWithFrame:backgroundColor: and property "backgroundColor" then its own overriden "setup" will be called by super initializator but "backgroundColor" will still nil. So this "setup" will cant use this property. I think its common problem and its have nice common solution, that i cant find.Thanks.
Typically, I'll create static function called _commonInit(MyClass *self) and call that from each initializer. It is a function because it won't be inherited.
base class
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
-(instancetype)initWithCoder:(NSCoder*)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
-(void)setup
{
//some setup code
}
child class
-(instancetype)initWithFrame:(CGRect)frame param:(id)param
{
self = [super initWithFrame:frame];
if(self)
{
self.param = param;
//setup will be run by parent
}
return self;
}
-(void)setup
{
[super setup];
//child setup code
//when this code will work self.param will still nil!
}
thats what i mean
I made a test app to understand how exactly init methods work. In my simple UIViewController I call the following:
- (id)init {
self = [super init];
self.propertyArray = [NSArray new];
NSLog(#"init called");
return self;
}
The above does not print any values in NSLog. However, when I write :
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
NSLog(#"init called");
self.propertyArray = [NSArray new];
return self;
}
It does print "init called" in console. So my question is: why is the init method called and the other is not? Which one do I have to use, when i want to do my stuff before the view loads (and any other methods called)?
Any explanation will be appreciated, thanks.
To begin with, you mention ViewController in your question. A UIViewController's designated initializer is initWithNibName:bundle:
You would never want to override just init on a UIViewController.
There is a lifecycle for each object:
When initializing in code, you have the designated initializer. Which you can find in the documentation for that class. For NSObject derived classes this would be init:
- (id)init
{
self = [super init];
if (self) {
// perform initialization code here
}
return self;
}
All objects that are deserialized using NSKeyUnrchiving, which is what happens in the case of Storyboard's or NIBs(XIBs), get decoded. This process uses the initWithCoder initializer and happens during the unarchiving process:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// perform initialization code here
}
return self;
}
It is common, because of this lifecycle, to create a shared initializer that gets called from each initializer:
- (void)sharedInit
{
// do init stuff here
}
- (id)init
{
self = [super init];
if (self) {
[self sharedInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self sharedInit];
}
return self;
}
To take it one step further. In the case of Storyboard's and XIBs, if you want to perform initialization or updates AFTER the unarchiving is completed and to guarantee all your outlets and actions are connected, you would use awakeFromNib:
- (void)awakeFromNib
{
// do init or other stuff to be done after class is loaded from Interface Builder
}
When a class is instantiated in your code, you pick which initializer to call, depending on your needs. When a class is instantiated through framework code, you need to consult the documentation to find out what initializer would be called.
The reason that you see the behavior that you describe is that your view controller is in a storyboard. According to Cocoa documentation, when a view controller is instantiated through a storyboard, its initWithCoder: initializer is called. In general, this call is performed when an object gets deserialized.
Note that it is common to check the result of self = [super initWithCoder:aDecoder]; assignment, and skip further initialization when self is set to nil.
When you load view controller from nib file (and storyboard) it uses initWithCoder: so in your example this is why it call this method.
If you create your view controller programatically this method won't work and you should override initWithFrame: initialiser instead and also you should create view controller by calling
[[UIViewController alloc] initWithFrame:...];
The different inits are different constructors. As in any other language, an instance is instantiated by the most appropriate constructor. That's initWithCoder: when restoring from an archive.
As a style point, note that use of self.propertyArray in a constructor is considered bad form. Consider what would happen if a subclass overrode setPropertyArray:. You'd be making a method call to an incompletely instantiated object. Instead you should access the instance variable directly, and perform the idiomatic if(self) check to ensure it is safe to do so.
Recently I am doing a project on iOS, I have created a class, namely YellowTileView, I would like to do something like when I clicked on button, a new tile will be shown
-(IBAction)ShowImage:(id)sender
{
YellowTileView *yt=[[YellowTileView alloc] initWithFrame:CGRectMake(0, 0, 60, 80)];
[self.view addSubview:yt];
}
This work fine for me. But the next step is to take a integer number that randomized by another method.
My question is can I redefine/create the method initWithFrame by myself? If yes, how can I do this and would it be any problems as I have some drawing code in the class YellowTileView?
First of all you should know that you can reimplement all methods of parent. So you can reimplement initialization method of UIView defined like this:
- (id)initWithFrame:(CGRect)frame;
But you can also create your own initialization method with your own parameters list.
In .h file:
- (id)initWithFrame:(CGRect)frame andWithRandomInt:(int)random;
And in .m file:
- (id)initWithFrame:(CGRect)frame andWithRandomInt:(int)random {
self = [super initWithFrame:frame];
if (self) {
_random = random;
}
return self;
}
You absolutely can - it should look something like this :
- (id)initWithFrame:(CGRect)frame andNumber:(int)number {
self = [super initWithFrame:frame];
if (self) {
myNumber = number;
}
return self;
}
As long as you call a super init... method somewhere in your constructor you'll be fine.
Also, it's common practice to name your methods like this:
-(IBAction)showImage:(id)sender
So, methods names start with a lowercase letter. Note that you don't have to - it's just that all the other developers do that.
I've been trying to save strings in Xcode such as email and password so i can open them in different view, So far every attempt for the past 2 weeks have failed.
does anyone have a working way, and if so can you post the code.
Thanks
*edit***
Almost done just having an error here
(MemberPage *)initWithString: (NSString) S {
self = [super init];
if ( self ) {
//DO STUFF;
UserNAME.text = S.text;
}
return self;
}
error is on the first line:
use of undeclared identifier with initWithString
Also get should be a ; before :
(MemberPage *)initWithString: (NSString *) s {
self = [super init];
if ( self ) {
//DO STUFF;
UserNAME = s;
}
return self;
}
Forgot the '*'?
One of the easiest way's I've done this in the past is just to pass them in when I create my View:
RecieverClass.m:
(RecieverClass*) initWithString: (NString) S {
self = [super init];
if ( self ) {
//DO STUFF;
myLocalString = S;
}
return self;
}
SenderClass (where you create your view)
RecieverClass *recieverClass= [[RecieverClass alloc] initWithString:sendString];
[[self navigationController] pushViewController:recieverClass animated:YES];
[recieverClass release];
You could pass them in as pointers or w/e really. Just depends what you're trying to do really.
I use a singleton class to share data between different views and for me it works. In the source viewController, I assign the value as a parameter to the "shared" class, and in the destination viewController I retrieve it.
Dunno if it's the "legal" way to do it, but it's simple and it works.
Check out this tutorial: http://www.bit-101.com/blog/?p=1969
At a certain point you arrive at this piece of code:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
Model *model = [Model sharedModel];
[model addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
Adding the observer didn't immediately work for me, dunno why but I didn't look into this issue deeper.
I changed it into:
Model *model = [Model sharedModel];
model.parameter = #"btnMainToTarget";
Follow the instruction from beginning till end - it will work.
To any other people; don't hesitate to react if you didn't think my reply was accurate.
Greetings