Designated initializer and initWithFrame - objective-c

I've created a class with designated initializer defined that way:
-(id)initWithFrame:(CGRect)frame
aProperty:(float)value {
self = [super initWithFrame:frame];
if(self){
///....do something
}
}
Now I wonder how to avoid direct calls to initWithFrame method, like:
MyClass *theObj = [MyClass alloc]initWithFrame:theFrame];
I thought about throwing exception in initWithFrame definition:
- (id)initWithFrame:(CGRect)frame
{
#throw ([NSException exceptionWithName:#"InitError"
reason:#"This class needs to be initialized with initWithFrame:aProperty: method"
userInfo:nil]);
}
Is this a good solution ?

What about calling your designated initializer with a default value for aProperty ?

Two techniques I use with initializers. I'm sure there a more.
Technique 1:
In initWithFrame call your initializer with a default value for a Property
- (id)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame aProperty:1.0f];
}
Technique 2:
Simply return nil;
- (id)initWithFrame:(CGRect)frame
{
return nil;
}

Related

Why can a factory method call an instance method?

As we all know, factory methods can't call instance methods. Why does the code below work?
// .m file implementation DemoClass
// custom instance init method
- (instancetype)initWithDate:(NSDate *)date {
if (self = [super init]) {
self.lastTime = date;
}
return self;
}
// custom factory method
+ (instancetype)DemoClassWithDate:(NSDate *)date
//here calling instance method initWithDate:
return [[self alloc] initWithDate:date];
}
[self alloc] will return an instance. initWithDate is just an instance method. No reason why a class method wouldn't be allowed to call an instance method on an instance.
PS. I would highly recommend that you check your compiler settings and tell the compiler to give you a warning if the result of '=' is used as a boolean value. This will prevent many hard to find bugs. You'll have to change the if to
if ((self = [super init]) != nil)
Because it has a reference to the, newly created, instance:
return [[self alloc] initWithDate:date];
// ^^^^^^^^^^^^
// reference

Designated initializer should only invoke a designated initializer on 'super'

This code is a modified version of Ordered Dictionary implemented here.
https://github.com/nicklockwood/OrderedDictionary/tree/master/OrderedDictionary
Interface -> OrderedDictionary.h
#interface OrderedDictionary : NSMutableDictionary
{
}
Implementation -> OrderedDictionary.m
// This is the new method added.
- (instancetype)init
{
return [self initWithCapacity:0];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
self = [super init];
if (self != nil)
{
// Allocate here.
}
return self;
}
The code works fine but I get following warnings in "- (instancetype)init".
Designated initializer should only invoke a designated initializer
on 'super'
Designated initializer missing a 'super' call to a
designated initializer of the super class
What am I doing wrong and how do I fix it?
Made following changes to the code to fix the problem
// This is the new method added.
- (instancetype)init
{
self = [super init];
if (self != nil)
{
// Allocate here.
}
return self;
}
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
self = [super initWithCapacity:capacity];
if (self != nil)
{
// Allocate here.
}
return self;
}
try adding
- (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER;
To the #interface block. All initialization paths should flow through the designated initializer - in your case initWithCapacity: is the obvious choice since init calls it. You may still run into problems, the designated initializer must call the super class's designated initializer. If the super class defines designated initializers those must be implement by your class as well...
In general subclassing NSMutableDictionary is considered bad practice because NSMutableDictionary is the public facing piece of a class cluster. This may be part of the cause of the compiler warnings.
As you can read in NSMutableDictionary documentation, there are two designated initializer for this class:
initWithCapacity: Designated Initializer
init Designated Initializer
Here you are calling from initWithCapacity in your class to super.init. That's the reason the compiler warns you.
This code maybe is better:
// This is the new method added.
- (instancetype)init
{
return [self initWithCapacity:0];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
self = [super initWithCapacity:capacity];
if (self != nil)
{
// Allocate here.
}
return self;
}
I'd bet some of you are tripping over this warning message when you have Xcode convert your code to "Modern Objective-C Syntax."
I've seen cases where Objective-C header files have been modified by the converter to have two designated initializers:
- (instancetype) init NS_DESIGNATED_INITIALIZER; // DO NOT USE
- (instancetype) initWithFileset: (NSArray *) fileset NS_DESIGNATED_INITIALIZER; // use this to instantiate
If you modify this code to a single initializer, you may find the warning message goes away:
- (instancetype) init; // DO NOT USE
- (instancetype) initWithFileset: (NSArray *) fileset NS_DESIGNATED_INITIALIZER; // use this to instantiate
In short, check the header and the source file when you see this warning message.
You should only call the super of your own function.
So replace
self = [super init];
with
self = [super initWithCapacity:capacity];

How does initWithNibName work?

I use to create my views programmatically and have started to switch using XIB files. I found this code:
-(id)init
{
self = [super initWithNibName:#"HelpViewController" bundle:nil];
if (self != nil) {
// further initialization needed
}
return self;
}
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
NSAssert(NO, #"Initialize with -init");
return nil;
}
It works but why? If I follow the logic, the initWithNibName returns nil and sets it to self. So, self is now nil and then you return self at the end of init. Well, that means you return self which is nil. Is that right?
Also, if I wanted to initialize a NSArray, where should I put it in that init function?
Thanks for the explanation.
Yko
Because the init method calls the self = [super initWithNibName...]. So You must call the init method to create the object. If you use initWithNibName it will fails
For Array you should initialize in init method
-(id)init
{
self = [super initWithNibName:#"HelpViewController" bundle:nil];
if (self != nil) {
// further initialization needed
myArray = [[NSMutableArray alloc] init];
}
return self;
}
You're looking at two different initWithNibName functions.
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
NSAssert(NO, #"Initialize with -init");
return nil;
}
The above function is overriding the superclass version of initWithNibName. It raises an assertion informing the caller to use init.
self = [super initWithNibName:#"HelpViewController" bundle:nil];
The above line is calling the superclass version of initWithNibName, which returns a view controller.
If you wanted to initialize an array, you would initialize it where the "further initialization needed" comment is.
It works because you are calling -initWithNibName:bundle: on super (most likely UIViewController), rather than on self (your subclass of UIViewController). If you were to call initWithNibName:bundle on self, then you would hit the assertion or return nil if you have disabled assertions. The superclass implementation of -initWithNibName:bundle: is not affected by your implementation and therefore continues to behave as it normally would.

Preventing subclasses overriding methods

Assume there is an object that initialises like so
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
and it has a subclass which is inited in a similar way
- (void)doInit
{
NSLog (#"In SubClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
Now if I create an instance of child class then I receive the following output:
In SubClass init
In SubClass init
when really, what I meant to happen is
In BaseClass init
In SubClass init
Is there a way to mark doInit to say that it shouldn't be overridden or do I need to create a unique name for all methods in a subclass?
I'm not entirely sure how I haven't come across this issue before, but there you go.
Edit:
I understand why this is happening, I hadn't expected that the base class would be able to call the overridden function.
I also can't just call [super doInit]; from the Subclass method because the BaseClass still needs to call doInit so that creating an instance of BaseClass will still work. If I called [super doInit], I'd still end up getting SubClass's doInit called twice.
It appears the answer is no and I'll just have to uniquely name each doInit like doBaseClassInit and doSubClassInit.
If you have a method that you don't want to by dynamically bound (i.e. don't want a subclass method to be called if it exists), you need to do it as a C function instead. So, you could do this instead:
In the base class:
static void DoInit(BaseClass *self)
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
in the subclass:
static void DoInit(SubClass *self)
{
NSLog(#"In SubClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
Note that both the DoInit methods are marked as static, so they are only visible each compilation unit (.m file) and don't conflict with each other.
You could, perhaps, try something like this in your base class. It would mean any time the init implementation inside BaseClass executed, the doInit implementation for BaseClass would be called.
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
Class baseClass = [BaseClass class];
SEL selector = #selector(doInit);
IMP baseClassImplementation = class_getInstanceMethod(baseClass, selector);
baseClassImplementation(self, selector);
return self;
}
As I mentioned in my comment, if that's the narrowness of your need this should work as it gets around the dynamic method lookup involved with sending a message. Hope this helps!
EDIT:
Disclaimer - if you're in this situation it's probably not a good sign for the longevity of your design. This technique will get you up and running for now but please document it carefully, and consider ways to refactor your code so this is no longer used. Consider fixes like these to really be used only when extremely urgent.
The reason why you are not getting the "In BaseClass init" console message is because your subclass is not calling the super's implementation of doInit.
If you don't want doInit overridden the 'best' way to avoid doing so is to not publish the existence of this method. Remove it from your header and uniquely name the method so that a collision is unlikely. For example, many of the private methods in Apple's frameworks have a leading underscore. So, for example, you could call your method _doInit and it will be very unlikely that a subclass accidentally create it's own overiding implementation.
Nope, there's no enforceable way to prevent a subclass from overriding a method. The best you can do is to avoid putting it in the public header file for the class so someone is not likely to try to override it. If the method has to be public, you just put a note in the documentation for the method that it shouldn't be overridden or that subclasses should call super's implementation whichever the case may be. You'll find these kind of instructions all over in the documentation for Apple's own classes.
If you want your subclass to use the baseclass version of doInit then in the subclass don't declare a doInit method. Here's what I mean:
ClassA:
#interface ClassA :
-(void) doInit;
-(id) init;
#implementation
-(void) doInit {
NSLog(#"ClassA doInit");
}
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
ClassB
#interface ClassB : ClassA
-(id) init;
#implementation
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
And really you don't need to override the init method as well unless there's some special code that you want that class to do.

How do you perform additional initialization after calling the designated initializer in Objective-C? (self = [self...)

Suppose I have a designated initializer that does some initialization as such:
- (id)initWithBlah:(NSString *)arg1 otherBlah:(NSArray *)arg2
{
if (self = [super init])
{
...
}
return self;
}
and I have another initializer that needs to call this, but then perform some other setup task:
- (id)initWithSomeOtherBlah:(void *)creativeArg
{
// Is this right? It seems to compile and run as expected, but feels wrong
self = [self initWithBlah:nil otherBlah:nil];
if (self)
{
[self someProcessingForThisInitDependentOnSelfInit:creativeArg];
}
return self;
}
Since the test to make sure the return value is correct, should 'self' be used in this context? I'm wondering if this is even a valid combination of events. We have a situation where we have an initializer that needs to perform some additional setup after the designated initializer is ran.
I'm wondering if the correct approach is to shove this additional processing in the designated initializer..
Please let me know if more clarification is needed. I was trying to keep this simple. :)
Thanks!
A general rule of thumb that I follow is that the designated initializer is the initializer with the most parameters and the other initializers chain down to the designated initializer.
In your example you are not using creativeArg in your initWithSomeOtherBlah constructor. I am not sure if that was intentional or not.
With this approach you are being explicit with your intentions when creating an object instead of side effect programming.
For example:
#implementation BlaClass
- (id)initWithBlah:(NSString *)arg1 otherBlah:(NSArray *)arg2 creativeArg:(void *)arg3
{
if (self = [super init])
{
self.arg1 = arg1;
self.arg2 = arg2;
self.arg3 = arg3;
[self someProcessingForThisInitDependentOnSelfInit:arg3];
}
return self;
}
- (void)someProcessingForThisInitDependentOnSelfInit:(void *)creativeArg
{
if(creativeArg == NULL) return;
//do creative stuff
}
- (id)initWithSomeOtherBlah:(void *)arg
{
return [self initWithBlah:nil otherBlah:nil creativeArg:arg];
}
...
#end
If you need two initializers in your class which initialize the class somewhat differently, a good coding practice is to identify the setup tasks that both initializers need to perform them, and move them to a separate method. This way, you don't need to call one custom initializer inside another. Here is how you need to do it:
-(void) setupBlah
{.....}
- (id)initWithBlah:(NSString *)arg1 otherBlah:(NSArray *)arg2
{
if (self =[super init])
{
[self setupBlah];
//Do other initialization
....
}
return self;
}
- (id)initWithSomeOtherBlah:(void *)creativeArg
{
if (self = [super init])
{
[self setupBlah];
//Do other initialization
.....
}
return self;
}
There is nothing wrong with calling another initializer from a non-designated initializer, see Apple's docs here.
In the case that I have two or more designated initializers (such as a view with initWithFrame: and initWithCoder:), I have found myself to define a setUp method that I call from both initializers, which is just a shorter name for your someProcessingForThisInitDependentOnSelfInit method.