perrigal
perrigal

Reputation: 53

ARC and Objective C Subclassing

I am quite new to Obj C and iOS development and I have come across an issue to which I have no understanding why it is happening.

So to set the scene, I have 2 model classes, Player and Computer Player and a Controller.

Player:

@interface Player : NSObject
-(void) playerMessage;
@end

ComputerPlayer:

@interface ComputerPlayer : Player
-(void) computerPlayerOnlyMessage;
@end

Controller:

@interface viewController : UIViewController{
Player *p1;
Player *p2;
Player *currentPlayer;
}


@implmentation ViewController
-(void)viewDidLoad
{
 p1 = [[Player alloc]init];
 p2 = [[ComputerPlayer alloc]init];

 [p1 playerMessage];
 currentPlayer = p2;
 [currentPlayer computerPlayerOnlyMessage];
}

However the issue with the above is [currentPlayer computerPlayerOnlyMessage] gives a complier error when ARC is turned on. When ARC is turned off it gives a compiler warning but will run as I would expect it too.

Any help is appreciated to get help me figure why this behaviour is happening.

Upvotes: 0

Views: 145

Answers (6)

Injectios
Injectios

Reputation: 2797

Isn't it better to define:

- (void)playerMessage;

method in ComputerPlayer class and:

-(void)playerMessage {
   [super playerMessage];
   
   [self computerOnlyPlayerMessage];

}

That's a point of inheritance, isn't it? You defined (expecting) your class variable as Player but NOT ComputerPlayer, and if it is ComputerPlayer it will execute specific work only for "computer".

Of course then you execute:

[Player playerMessage]; // Warning should gone

Upvotes: 2

greymouser
greymouser

Reputation: 3181

This seems like a good place to use protocols.

Here's how I might write your example, where I need to send "player" messages to all instances of Players, specialize on occasion, and send specific "npc" messages other times.

@protocol <NPC>
@property (nonatomic) NSString *someNPCString;
- (void)foo;
- (void)bar;
@end

@interface Player : NSObject
@end
@implementation Player
- (void)message
{
    NSLog(@"message");
}
@end

@interface AI : Player <NPC>
@end
@implementation AI
@synthesize someNPCString;
- (void)foo
{
    NSLog(@"foo");
}
- (void)bar
{
    NSLog(@"bar");
}
@end

@interface viewController : UIViewController
@property (nonatomic) NSArray *humans;
@property (nonatomic) NSArray *npcs;
@end
@implmentation ViewController
-(void)viewDidLoad
{
    id<Player> currentPlayer = nil;
    humans = [NSArray arrayWithObject:[Player new]];
    npcs = [NSArray arrayWithObjects:[AI new], [AI new], nil];

    // message all player types, regardless of player or npc
    for (Player *player in [humans arrayByAddingObjectsFromArray:npcs])
    {
        currentPlayer = player;
        [player message];
        if([player conformsToProtocl:@protocol(NPC)])
        {
            [(id<NPC>)player foo];
        }
    }

    for (id<NPC>npc in npcs)
    {
        [npc bar];
        npc.someNPCstring = @"str";
    }
}

As you can see, this lets you treat npcs like human players, if you need to, let's you ask if the player conforms to the NPC protocol, so you may call the required protocol methods, and let's you reference objects specifically by their protocol.

Protocols begin to make the most sense, in my humble opinion, when you begin to need to "mix in" behavior to various objects.

Upvotes: 0

Antonio
Antonio

Reputation: 72780

As @Greg said, computerPlayerOnlyMessage is a method exposed by the ComputerPlayer class, not the class it inherits from, so even if the compiler reports a warning when ARC is disabled, it would be a bad practice to use it.

Explicitly asking the class instance if it implements that method it's a workaround that works though. However in my opinion that solution lacks of good OO design, and I wouldn't use it unless I have a good reason (there are cases when it is handy) - in other OO languages that wouldn't event be possible.

Polymorphism allows an instance of a class to be used as if it were one of its super classes, but not the opposite. You can override and specialize the superclass methods, but you cannot expect a superclass to be aware of methods implemented by any of its subclasses.

I suggest 2 possible solutions:

  1. declare computerPlayerOnlyMessage in the Player class as abstract (with empty body or throwing an exception, acting as a reminder that the method should be overriden in subclasses)
  2. remove computerPlayerOnlyMessage from ComputerPlayer and instead override playerMessage. Thanks to polymorphism, the correct implementation will be called, regardless of whether you are accessing to the class instance as Player or ComputerPlayer

If computerPlayerOnlyMessage is meant to do what playerMessage does, just in a different way, I'd choose option no. 2

Upvotes: 0

Popeye
Popeye

Reputation: 12113

First instead of declaring them as ivars like

@interface viewController : UIViewController{
    Player *p1;
    Player *p2;
    Player *currentPlayer;
}

do it with @properties. The reason being is that ivars don't have any getters or setters whereas they are automatically generated if you use '@properties so change to

@interface viewController : UIViewController
// This will auto generate the ivars, getters and setters
@property (nonatomic, strong) Player *p1;
@property (nonatomic, strong) Player *p2;
@property (nonatomic, strong) Player *currentPlayer;
@end

then you can do

@implmentation ViewController
-(void)viewDidLoad
{
    p1 = [[Player alloc]init];
    p2 = [[ComputerPlayer alloc]init];

    [p1 playerMessage];
    currentPlayer = p2;

    // Could also replace below with [currentPlayer isKindOfClass:[ComputerPlayer class]] use which ever fits best for you and what you want.
    // If using below, if you decided to subclass ComputerPlayer class anything that subclassed 
    // from ComputerPlayer will also make it into this if statement. If iskindOfClass: is used 
    // Only objects that are kind of class will make it into this if statement.
    if([[currentPlayer class] isSubclassOfClass:[ComputerPlayer class]]) {
        [(ComputerPlayer *)currentPlayer computerPlayerOnlyMessage];
    }
}

Upvotes: 0

vikingosegundo
vikingosegundo

Reputation: 52237

You can test, if it is a computer player

 if ([currentPlayer isKindOfClass:[ComputerPlayer class]])
     [(ComputerPlayer *)currentPlayer computerPlayerOnlyMessage];

Upvotes: 1

Greg
Greg

Reputation: 25459

It gives you an error because p2 is subclass of Player and you haven't for such a method as computerPlayerOnlyMessage in Player class. This method exists in ComputerPlayer class so you should declare p2 as a object of this type. Chenge line where you declare p2 to:

ComputerPlayer *p2;

Upvotes: 0

Related Questions