different init methods being called - ios

I'm a little about this. I'm initializing a UITableViewController subclass CTSettingsVC via:
CTSettingsVC *settingsVC = [[CTSettingsVC alloc] init];
However, the initWithStyle initializer is being called. This is my m file:
#interface CTSettingsVC ()
#end
#implementation CTSettingsVC
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
NSLog(#"Why is this called?")
self.title = #"Settings";
}
return self;
}
#end
I'm not sure if this is the intended behaviour or if I'm missing something.

This happens because initWithStyle: is the designated initializer of UITableViewController. The init method of UITableViewController looks something like this:
- (instancetype)init
{
//NOTE: self is being used instead of super
return [self initWithStyle:UITableViewStylePlain];
}
Not all UIKit classes follow this convention of overriding init with default values for the designated initializer when one exists. I often do it myself if I plan on using init for a default initializer to ensure my subclasses are not broken in future versions of UIKit.

init and initWithStyle: are convenience initializers for UITableViewController. Internally they look something like this:
- (instancetype)init
{
return [self initWithStyle:UITableViewStylePlain];
}
- (instancetype)initWithStyle:(UITableViewStyle)style
{
self = [self initWithNibName:nil bundle:nil];
if (self) {
_tableViewStyle = style; // Private, used in loadView.
}
return self;
}
The exception where you will not see initWithNibName:bundle: called is when your view controller is instantiated by a storyboard. There, initWithCoder: is the initializer.

Related

Custom Constructor in Objective-C

I have created a custom class extending UIView. This class has some methods such as Drawerect...
Up to now, I was just putting it in my storyboard and telling that it belongs to the class. I would now allocate and place those objects dynamically. Is there a method so I could call :
[[MyObj alloc] initWithFrame:....]
Id be glad to find any help !
You can create your own constructor in the header file of your class.
The return value is of type id , in its declaration in the main file you need to call a super initialization (for example self = [super initWithFrame:CGRect]) and then return the self. You can customize the parameters of your constructor in the header file to fit your needs.
Example for UIView:
.h:
#import <UIKit/UIKit.h>
#interface CustomView : UIView
-(id)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor;
.m:
#import "CustomView.h"
#implementation CustomView
-(id)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor{
self = [super initWithFrame:frame];
if (self) {
//after allocation you could set variables:
self.backgroundColor = backgroundColor;
}
return self;
}
#end
When instances of UIView are unarchived from an Interface Builder document, their initWithFrame: method isn't called. Instead, the unarchiver calls initWithCoder:. Ideally you should override both methods, and have them call a common method that provides a shared implementation of the initialization code. That way the views will be initialized correctly whether they're instantiated programmatically, or as a result of being unarchived. Here's an example:
- (id)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])) return nil;
[self configureSubviews];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (!(self = [super initWithCoder:aDecoder])) return nil;
[self configureSubviews];
return self;
}
- (void)configureSubviews
{
// Custom configuration code...
}

super initWIthCoder return parent type?

I think I'm missing something basic...
I implemented a class with NSCoding and a child with NSCoding too, but when I call the initWithCoder of the child class , I get an InvalidArgument error.
#interface Parent: NSObject<NSCoding>;
#implementation Parent
-(id)initWithCoder:(NSCoder *)decoder {
self = [[Parent alloc] init];
return self;
}
#end
#interface Child: Parent<NSCoding>;
#implementation Child
-(id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder]; //self is Parent type here
// self = [[Child alloc] init]; if i do that, no error but no init for the parent'attribute
if(self){
self.childAttribute = [decoder decodeObjectForKey:#"KeyAttribute"]; // invalide argument ==> setChildAttribute doesn't exist.
}
return self;
}
I must have forgotten something basic, but I can't find out what...
Anybody have an idea?
Thanks.
You are initializing Parent in the wrong way. When -initWithCoder: is called, the class has already been allocated. Remember the syntax:
id myObj = [[MyClass alloc] initWithArgument:...];
so it is assumed that within initializers you don't allocate, you set up default values.
You can refer to the ObjectiveC documentation to see how this should be done. I strongly recommend to check this out: Concepts in ObjC Programming – Object Initialization.
Also the Memory Management Guide can be very helpful. ObjectiveC relies on several conventions that you should be aware of to avoid leaks that might become difficult to track.
The right way of initializing your parent is:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init]; // it's just a subclass of NSObject
if (self) {
// DECODE VARIABLES...
}
return self;
}
If Parent were a subclass of another NSCoding-compliant class, [super init] should have been replaced by [super initWithCoder:aDecoder]; but in no case within an initializer you set self to something that has not been returned by a superclass -init... method.
You get the error because when you call [Child alloc], an instance of Child is allocated, but then during initialization of Parent you return the Parent instance you manually allocated, therefore you lost the original reference to Child and the class does not match anymore.
The returned object from your Parent class initialization function is probably the cause. You need to keep initializing its parent with the initWithCoder: function. Now it should just be returning a simple NSObject object with no childAttribute property.
With all else hooked up properly it should just need to be:
#implementation Parent
-(id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
return self;
}
#end

Initialize and Allocate a Class that is visible to all methods

Greetings i need to init alloc an instance of a class and have it accessible by any method
Example for using "Whatever *boom = [Wathever alloc]init];"
#interface something : NSObject;
#implementation
-(void) method1{
boom.size = 10;
}
-(void) method2{
boom.color = blue;
}
Where would i alloc and init boom so that i can manipulate it in every method?
for example in whatever.h and whatever.m to call the methods of a class must be declared in whatever.h
-(void) method1;
-(void) method2;
and used
Whatever *boom = [Wathever alloc]init];
[boom method1];
[boom method2];
In a single class? Make it a property of that class.
//.h
#property Whatever *boom;
//.m
- (id)init {
self = [super init];
if (self) {
_boom = [[Whatever alloc] init];
}
return self;
}
Across your whole app? Create an instance of it somewhere, like your app delegate, and then pass it along to the Root View Controller, which in turns passes it to each View Controller.
// AppDelegate .m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// app setup code
Whatever *boom = [[Whatever alloc] init];
FirstViewController vc = self.window.rootViewController;
vc.boom = boom;
}
// FirstViewController.h, NextViewController.h, etc.
#property Whatever *boom;
// FirstViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NextViewController *nextVC = sender.destinationViewController;
nextVC.boom = self.boom;
}
You could also go the Singleton route, but then you are tightly coupled to a single instance of the class app-wide.
See when you create a class in that to intialize that class a common method will always be there which calls that class it self:
Something like this :
-(id)init
{
self = [super init];
if (self)
{
}
return self;
}
You can declare the instance in .h file like this :
Whatever *boom;
Than you can initialize that instance in above method as following :
-(id)init
{
self = [super init];
if (self)
{
boom = [Wathever alloc]init];
}
return self;
}
hope this will help you.

UIVIew superclass object calling subclass method instead of method in its own scope

My super class defines a private method called "commonInit" which is only called at construction.
The super class is derived by 2 additional classes, each of which also implement a method called "commonInit"
While constructing the objects of the derived class I see in the debugger that the subclass method is called from the scope of the superclass.
This seems to be very dangerous behavior - even in a trivial case when by coincedence you "overwrite" your superclass private method
How can I overcome this behavior without renaming the method in the super class?
Example:
#interface ASuperView : UIView
#end
#implementation ASuperView
-(id)init
{
self = [super init];
if(self)
{
[self commonInit]; // BOOM - The derived view method is called in this scope
}
return self;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self commonInit];
}
return self;
}
-(void)commonInit
{
//setup the view
}
#end
#interface ADerivedView : ASuperView
#end
#implementation ADerivedView
-(id)init
{
self = [super init];
if(self)
{
[self commonInit];
}
return self;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self commonInit];
}
return self;
}
-(void)commonInit
{
//setup the view for the derived view
}
#end
In this image PXTextMessageBox derived from PXTextBox
Both declare privately the method common init
There is no such thing as 'private' methods in obj-c. At best you can hide the existence of a method from consumers of your header, but by design anyone that has a reference to your object can call any method it implements - even if they don't have that method defined in the header. Your best bet will be to define a new method, say _private_commonInit, and not share that in your class header.
I believe this is actually by design. Polymorphism at its best even! .. self actually refers to the object that originally sent the message (which is not always the class instance where self appears) ... one way to solve this would be to chain the commonInit in the same way Init is chained ... a call to [super commonInit] will invoke the correct method from the subclass ...

viewDidLoad never called in Sub-classed UIViewController

I've run into some trouble with loading a sub-classed UIViewController from a nib. My viewDidLoad function is never called.
The super-class has no nib, but the sub-classes each have their own. i.e.
#interface SuperClass : UIViewController {
}
#end
#interface SubClass : SuperClass{
}
#end
#implementation SubClass
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad{
// Never called
}
The view is loaded from a nib as follows:
SubClass *scvc = [[SubClass alloc] initWithNibName:#"SubClass" bundle:nil];
[self.navigationController pushViewController:scvc animated:YES];
[scvc release];
There is a nib file with this name and it has it's file owner's class set properly.
viewDidLoad is not called in the child or super. Any ideas?
You say you are creating views in code (using loadView) in the super class, but trying to use a nib in the sub class?
According to the UIViewController docs (emphasis mine)
If you specify views using a nib file, you must not override loadView but should instead create a nib file in Interface Builder...
It looks like you may have a conflict there. As a test, comment out the loadView method in your superclass and see where that gets you.
I'm assuming SuperClass implements loadView. So when your SubClass is asked to loadView, you get your SuperClass' implementation, which overrides the normal nib loading mechanism.
I would rethink your design here. Your SuperClass' behavior is quite different from what you want your SubClass to do. Maybe that's not the best relationship there.
But, if you want, you should be able to at least make it work by doing this in your SubClass.m:
-(void)loadView {
IMP defaultImp = class_getMethodImplementation([[self superclass] superclass], _cmd);
/* or alternatively...
IMP defaultImp = class_getMethodImplementation([UIViewController class], _cmd);
*/
defaultImp(self, _cmd);
}
This implements a loadView for your subclass that skips over the loadView of your SuperClass and calls the default implementation instead.
IMO, this is ugly and might need to revisited if your class hierarchy expanded. I wouldn't do this in my own app, I would rethink the class hierarchy instead.
Where is this block located
SubClass *scvc = [[SubClass alloc] initWithNibName:#"SubClass" bundle:nil];
[self.navigationController pushViewController:scvc animated:YES];
[scvc release];
Are you sure it is called ?
I made a testproject to test it and the following works:
In your appDelegate.m file i put the following in the application didFinishLaunchingWithOptions method
UINavigationController *navController = [[UINavigationController alloc] init];
self.window.rootViewController = navController;
SubClass *viewController1 = [[[SubClass alloc] initWithNibName:#"SubClass" bundle:nil] autorelease];
[navController pushViewController:viewController1 animated:YES];
SuperClass.h
#import <UIKit/UIKit.h>
#interface SuperClass : UIViewController
#end
SuperClass.m
#import <UIKit/UIKit.h>
#import "SuperClass.h"
#implementation SuperClass
#end
SubClass.h
#import <UIKit/UIKit.h>
#import "SuperClass.h"
#interface SubClass : SuperClass
#end
SubClass.m
#import <UIKit/UIKit.h>
#import "SubClass.h"
#implementation SubClass
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"Methods %# called", NSStringFromSelector(_cmd));
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#end
This works for me, make sure you set the right class for your nibFile aswell. Click on the file's owner icon and change the class from UIViewController to SubClass
Try to add:
[super viewDidLoad];

Resources