Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Subclassing a Custom Class

I'm having trouble wrapping my thoughts about class inheritance. I'm suppsed to create a dashboard like interface in a app, and I'll have maybe 10 widgets/dashlets on that dashboard view. All those dashlets/widgets will have basically same look, with a title on the top, borders, row of buttons on the top and a graph. Let's say I create a subclass of UI View called 'Dashlet' with properties and outlets, and create XIB file with proper layout and connected outlets etc.

Now I want to create several subclasses of that 'Dashlet' view that will only process data differently, and draw different graphs. My current code looks something like this:

Dashlet.h

@interface Dashlet : UIView{
@private
    UILabel *title;
    UIView *controls;
    UIView *graph;    
}
@property (weak, nonatomic) IBOutlet UILabel *title;
@property (weak, nonatomic) IBOutlet UIView *controls;
@property (weak, nonatomic) IBOutlet UIView *graph;

-(Dashlet*)initWithParams:(NSMutableDictionary *)params;
-(void)someDummyMethod;
@end

And in Dashlet.m

- (id) init {
    self = [super init];
    //Basic empty init...
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

    }
    return self;
}

-(id)initWithParams:(NSMutableDictionary *)params
{
    self = [super init];
    if (self) {
        self = [[[NSBundle mainBundle] loadNibNamed:@"Dashlet" owner:nil options:nil] lastObject];
        //some init code
    }
    return self;
}

Now let's say that I create a subclass called CustomDashlet.h:

@interface CustomDashlet : Dashlet
@property (nonatomic, strong) NSString* test;
-(void)testMethod;
-(void)someDummyMethod;
@end

and CustomDashlet.m

-(id)init{
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

    }
    return self;
}

-(id)initWithParams:(NSMutableDictionary *)parameters
{
   self = [super initWithParams:parameters];
   if (self) {
      //do some stuff 
   }
   return self;
}

This, kind of works, but I need to override some of the methods declared in the superclass or even add some of my own. Whenever i try to do something like this in CustomDashlet.m

[self someDummyMethod] or even [self testMethod] I get an exception error like this:

NSInvalidArgumentException', reason: '-[Dashlet testMethod]: unrecognized selector sent to instance 

Am I even doing this right? Did I miss something? Am I supposed to make this work in some other way? If anyone has suggestions, please feel free to share your thoughts, thank you for all the help.

like image 877
Slavenko Miljic Avatar asked Nov 11 '13 18:11

Slavenko Miljic


4 Answers

The problem is that

SalesDashlet *sales = [[SalesDashlet alloc] initWithParams:nil];

does not return a SalesDashlet instance, as expected, but a Dashlet instance. Here is what happens:

  • [SalesDashlet alloc] allocates an instance of SalesDashlet.
  • The subclass implementation of initWithParams: is called with this instance, and calls self = [super initWithParams:parameters].
  • The superclass implementation of initWithParams discards self and overwrites it with a new instance loaded from the Nib file. This is an instance of Dashlet.
  • This new instance is returned.

Therefore SalesDashlet *sales is "only" a Dashlet, and calling any subclass method on it throws an "unknown selector" exception.

You cannot change the type of objects loaded in the Nib file. You could create a second Nib file containing a SalesDashlet object. If the main purpose of the subclass is to add additional methods, then the easiest solution would be to add these methods in a Category of the Dashlet class.

like image 134
Martin R Avatar answered Sep 28 '22 17:09

Martin R


If the problem is with the

- (Dashlet *)initWithParams:

method it is because the base class declares it with a Dashlet return value, whereas the subclass is redeclaring it with a SalesDashlet return instance.

Always use instancetype as the return type for any init method.

like image 39
Infinity James Avatar answered Sep 28 '22 17:09

Infinity James


I believe you simply need to change following line in your Dashlet.h file:

-(Dashlet*)initWithParams:(NSMutableDictionary *)params;

to following:

-(id)initWithParams:(NSMutableDictionary *)params;

or better:

-(instancetype)initWithParams:(NSMutableDictionary *)params;
like image 20
Yas Tabasam Avatar answered Sep 28 '22 19:09

Yas Tabasam


You need to change your init methods.

-(Dashlet*)initWithParams:(NSMutableDictionary *)params
-(SalesDashlet*)initWithParams:(NSMutableDictionary *)parameters

The return type on both of these should be id.

The problem you're running into is similar to trying to do this:

NSMutableArray *someArray = [[NSArray alloc] init];

Despite declaring someArray as an NSMutableArray, you've initialized it as an NSArray, and as such, someArray will actually be an immutable NSArray.

So because your SalesDashlet init method calls its super init method and the super explicitly returns an object of type Dashlet, then the SalesDashlet will also return an object of type Dashlet, so you're trying to call testMethod (a method that only exists in SalesDashlet) on an object of type Dashlet (which doesn't know about the testMethod method).

Changing your return type to id will make the methods return an object of the right type.


As a note, you've done your init, and initWithFrame methods correctly.

SalesDashlet *mySalesDashlet = [[SalesDashlet alloc] initWithFrame:someFrame];

Creating a SalesDashlet in this way will allow you to call [mySalesDashlet testMethod].

Your initWithFrame has return type of id in both super and sub classes.

like image 30
nhgrif Avatar answered Sep 28 '22 18:09

nhgrif