1

Is it possible at Objective C at init method to return an instance of different classes? I'm having a Class called: MyCustomClass. I also have two other different classes called Class 1 and Class2. What I'm trying to implement is: When I call [[MyCustomClass alloc] initWithSomeParameters to create instance of Class1 or Class2 depending on some condition.

MyCustomClass.m:

#import "MyCustomClass.h"
#import "Class1.h"
#import "Class2.h"

-(id) initWithSomeParameters: (id) params{
  id myClass;
  if (someCondition){
    myClass = [[Class1 alloc] initWithSomeParameters:(id) params];
    [myClass setSomething:something];
  }else{
    myClass = [[Class2 alloc] initWithSomeParameters:(id) params];
    [myClass setSomething:something];
  }
  return myClass;
}

...and later I call

id myCustomClass = [[MyCustomClass alloc] initWithSomeParameters:(id) params];

Is this a wrong approach? If so, what would be the correct one?

Sled
  • 18,541
  • 27
  • 119
  • 168
Ahmed Ahmedov
  • 592
  • 12
  • 29
  • 1
    possible duplicate of [What exactly is a so called "Class Cluster" in Objective-C?](http://stackoverflow.com/questions/1844158/what-exactly-is-a-so-called-class-cluster-in-objective-c) –  Nov 01 '13 at 13:43
  • Is `Class 1` and `Class 2` subclass of your `MyCustomClass` – Priyatham51 Nov 01 '13 at 13:44
  • Thanks for quick answer! No, Class1 and Class2 are not subclasses of MyCustomClass. Class1 is UIAlertView and Class2 is subclass of UIView. MyCustomClass is NSObject. – Ahmed Ahmedov Nov 01 '13 at 13:48
  • Have a loot at this. [Class cluster][1] seems to be what you are looking for. [1]: http://stackoverflow.com/questions/1844158/what-exactly-is-a-so-called-class-cluster-in-objective-c – Daniel Nov 01 '13 at 14:02
  • @H2CO3 This wouldn't be a class cluster, since they're not subclasses of `MyCustomClass`. – Aaron Brager Nov 01 '13 at 14:04
  • 1
    @AaronBrager right, but it would be bad class design.... That kind of logic should not be in an init method. – bbum Nov 01 '13 at 14:16
  • 2
    @bbum I agree; but it's not a duplicate of that question. – Aaron Brager Nov 01 '13 at 14:22
  • @AaronBrager Then that method should not have been called `initWithSomeParameters:`. That's highly misleading. –  Nov 01 '13 at 16:26

4 Answers4

3

Several others have mentioned this, but the result of calling [[MyClass alloc] init] must always be nil or a kind of MyClass. It doesn't have to specifically be an instance of MyClass; one of its descendants is possible, as with NSArray or NSString. In code, this requirement would look like:

MyClass *a = [[MyClass alloc] init];
NSAssert((a==nil) || [a isKindOfClass:[MyClass class]], @"This must always hold true.");

I've never attempted to implement this, but it would probably have to look something like this:

- (id)initAsSubclass:(NSString *)sublcassName
{
    Class c = NSClassFromString(subclassName);
    self = [[c alloc] init];
    if (self) {
        // Do Custom Init Here
    }
    return self;
}

The keys would be:

  • DO NOT perform [super init].
  • Create a completely new object with +alloc.
  • Assign the newly created object to self.
  • If not using ARC, perform [self autorelease], before replacing the value. (If the object that is currently executing code becomes deallocated, it can cause issues. -autorelease will defer that until this section is complete.)
Chuck
  • 234,037
  • 30
  • 302
  • 389
Holly
  • 5,270
  • 1
  • 24
  • 27
  • 1
    In rare cases, it is OK to return something that is a proxy to a kind of `MyClass` or otherwise behaves exactly like a `MyClass`. Both of which are advanced topics and rare enough to merely be a technical detail, not a criticism of the answer (which is dead on). You *can and should* use `[self release]; self = new;`; if invoked via `super` the superclass better had damned well be doing so via `self = [super init...];`. – bbum Nov 01 '13 at 15:53
  • The downside of your code is that it would allocate an extra object that is then discarded. – newacct Nov 01 '13 at 21:35
1

Its not a good approach. Its better use some helper class or us factory pattern and provide parameters to method. Then depending on parameters create an object of class and return.

Its not good approach to create object of different class in init method of different class.

Edit: if You want to show UIView or UIAlertView depending on iOS version do like this.

@interface AlertHelper : NSObject
+ (id)getAlert;
@end

///
@implementation AlertHelper
+(id)getAlert{
NSString *version = [[UIDevice currentDevice] systemVersion];
int ver = [version intValue];
if (ver < 7){
//For iOS 6
return something;
}
else{
//for ios 7
return something
}
}
@end
Adnan Aftab
  • 14,377
  • 4
  • 45
  • 54
  • 2
    if the class being returned is a concrete class of an abstract the class being "instantiated" then I don't see a problem. In fact this is what Apple does with `NSString` and many other classes. Its sort of an obj-c variation of a factory (called a class cluster). – Brad Allred Nov 01 '13 at 13:56
  • 1
    Yes your are right it can be, but this is an exception not a rule. Its better to have some static method to create object rather then doing this in init. If they are not in is_a relationship. I think doing this in code should be exceedingly rare or not done at all. – Adnan Aftab Nov 01 '13 at 14:00
  • @BradAllred The OP says that `Class1` and `Class2` aren't subclasses of `MyCustomClass`. – Aaron Brager Nov 01 '13 at 14:03
  • Brad Allred and C_X thank you both! C_X what i'm trying to achieve is to replace all existing UIAlertView occurrences in code with MyCustomClass. Will this be possible using the static method you mentioned? I'm supposed to use [MyCustomClass alloc] initWith..] and later [myCustomClass show]. – Ahmed Ahmedov Nov 01 '13 at 14:06
  • 2
    @AsenKasimov it sounds like what you are trying to do is what the old `poseAsClass:` method used to do. now you have to accomplish that using "is-a swizzling". this is what Apple does with KVO. – Brad Allred Nov 01 '13 at 14:14
  • "replace all existing UIAlertView occurrences in code with MyCustomClass." While it is ok in some cases to return a class that is different than the one requested (though it really should be a subclass), what you're describing isn't a good solution for this request. – Rob Napier Nov 01 '13 at 14:17
  • Also what you are trying to do probably won't be accepted by Apple if you are intending on publishing this app. – Brad Allred Nov 01 '13 at 14:20
  • Brad Allred why shouldn't be accepted by Apple? What i'm trying to do is depending on the version of iOS to show custom UIView or UIAlertView. Thanks! – Ahmed Ahmedov Nov 01 '13 at 14:25
1

You should make some kind of controller, which initializes correct classes. You can also achieve same that using class methods.

ANd in genreal this given implementation is bad, because you alloc memory once [MyCustomClass alloc] and then in -(id)initWithSomeParameters:(id)params you are allocating memory again. So, even different address will be retruned, that isn't agains apple guidelines, some apple classes also have such behavior, but they do it because of optimizations. But here it is wrong.

Vytautas
  • 573
  • 3
  • 8
  • Thanks for pointing out the allocating twice! I'm not sure is it possible to avoid this using the upper init method unless the UIAlertView has to be allocated in order to call initWithTitle... – Ahmed Ahmedov Nov 01 '13 at 14:28
  • Actually, you can create e.g. class method or normal method. Lets define class MyDesiredClassFactory and a method there ``-(id)giveMeMyDesiredClassWithParameters:(id)params``. Basically in this case you should apply **Factory** design pattern. It can be a simplified version of it, but just try keep code simple as possible, then later you will have less problems. – Vytautas Nov 01 '13 at 15:14
0

The way to do it is like this:

Create Base class like:

#import "Base.h"
#import "Class1.h"
#import "Class2.h"

@implementation Base

+ (id)classWithParams:(id)params
{
     id retVal = nil;    

     if (condition_based_on_params_means_creating_class1)
     {
        retVal = [[Class1 alloc] initWithSomeParameters:params];
     }
     else
     {
         retVal = [[Class2 alloc] initWithSomeParameters:params]
     }

     return retVal;
}

@end 

Class1 inherits from Base:

@interface Class1 : Base
{
}
- (id)initWithSomeParameters:(id)parameters;
@end

Class2 inherits from Base:

@interface Class2 : Base
{
}
- (id)initWithSomeParameters:(id)parameters;
@end

Ultimately you will have:

Base* a = [Base classWithParams:yourParams];
Daniel
  • 577
  • 2
  • 12
  • The OP is not asking how to make a class cluster. – Aaron Brager Nov 01 '13 at 17:23
  • This is exactly mine current implementation. I will mark this answer as the accepted one because calling a class method instead of the init method seems to be the only way to accomplish my specific task - (Class1 is an UIAlertView). One of the reasons for this is that -(void)addSubview:(UIView *)view is not called. No matter MyCustomClass is an NSObject, @ interface UIView(UIViewHierarchy)/-(void)addSubview:(UIView *)view is called instead. I will try (no idea is it possible) to accept @Neal's answer too because it is definitely answering the question i asked. Thanks to all! – Ahmed Ahmedov Nov 01 '13 at 21:05
  • The question specifically asked how to make an `init` method return instances of different classes. This doesn't answer the question. – newacct Nov 01 '13 at 21:38
  • It's not about the question in general. It's about what is what you want to do. If your question is partially wrong it is better to get a better idea/pattern instead of answering to the original question. – Daniel Nov 01 '13 at 21:44