Is it an acceptable template for an init method to return an object of a different type?
I am working on fixes for some existing objective-c code and came across what I found strange:
@interface ClassA : UIView
...
static ClassA* oldSelf = nil;
@implementation
- (id)initWithFrame:(CGRect)frame {
oldSelf = self;
self = [[ClassB alloc] initWithFrame:(CGRect)frame]; // xcode warns: Incompatible pointer types assigning to "ClassA *" from "ClassB *"
// ^^^^^^ Is this ok?
[oldSelf release];
return self;
}
@interface ClassB : UIView
...
@implementation
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
return self;
}
It's all wrapped up in an old library ... The public gets the file lib.a and ClassA.h In the code using the library, this happens:
#import "ClassA.h"
...
// useage
ClassA *myA = [[ClassA alloc] initiWithFrame:CGRectMake(0,0,100,100)];
...
So we have an initializer for ClassA that actually returns an unrelated class. ClassA and ClassB respond to the same messages, so they compile and run. It seems like ClassA is being used to shade some of the functions exposed in ClassB?
I am curious if this is acceptable behavior and if it is a known pattern, what is it called? Are there any side effects for this setup?
=============================================== === =======
Thanks for answers! I think I got it ... in short, not a normal sample, not exactly a good idea.
- Kind of like a cluster of classes (abstract factory), but not really, because a generic abstract class must be returned. And since the code never seems to be going to return anything other than a ClassB object, perhaps not what the original author was thinking.
- More like a proxy, but not implemented correctly. ClassA must contain a private instance of ClassB and pass messages between them.
=============================================== === =======
Edited: added "oldSelf" parts ...
Edited: Added static library details ...
Edited: Added accepted reply message ...
source to share
The main drawback I see here is: The user ClassA
expects the object he just created via [[ClassA alloc] initWithFrame:...]
to return YES for [object isKindOfClass:[ClassA class]
.
It can also lead to errors when using things like NSInvocation
because the wrong class will be used to define the method signature, although I'm not sure about that.
Due to the dynamic nature of Objective-C, this will work as you describe, but it can be confusing and I would strongly discourage anyone from using this pattern.
As pilavdzice said, the "correct" alternative would be to have both ClassA
and ClassB
inherit from another class ( abstact superclass ), which then in its initializer decides what the particular subclass is . Good examples of this pattern, called cluster clusters, are NSString
, NSArray
and NSDictionary
, which return objects of different subclasses based on their initialization, which is also the reason why you cannot subclass them directly without any effort.
source to share
This is not an unreasonable thing in all cases, but it is difficult to say if this is a good thing in the situation you are describing. Two examples where this might be good:
-
The initializer returns an instance of a more specialized subclass. For example, you can choose different implementations of the data structure depending on the number of items to store.
-
The initializer returns some kind of proxy object.
Your code looks a little weird. At the very least, I expect to see the cast as a signal (for both the compiler and future programmers) that the author knew what he was doing. A comment explaining why a different object type was returned would not hurt either. Ideally ClassB
should be a subclass ClassA
as it was supposed to provide the same interface.
source to share
In user code, of course, it is not easy to return an unrelated class, however, in some of the Apple frameworks, a more specific version of the class with the same public interface is common.
Apple Cocoa Fundamentals discusses in some detail the fact that objects like NSArray and NSNumber can return a different object than the class you're asking for.
source to share
Well, it's kind of like NSScanner .
Thus, the inner class is not affected and cannot be misused. ClassB cannot be initialized anywhere other than the ClassA implementation file.
This makes sense if you have multiple inner classes and your initializer is somehow deciding which class is actually needed.
I don't see any benefit if you only use one inner class.
source to share