0

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.

perrigal
  • 53
  • 4
  • 3
    Please add the warning. Additionally: Why do you use explicit ivars? Why don't you use accessors? Wh don't you use declared properties? – Amin Negm-Awad Feb 21 '14 at 10:38
  • See [Why ARC forbids calls to undeclared methods?](http://stackoverflow.com/questions/20582642/why-arc-forbids-calls-to-undeclared-methods) for some explanations why you get an error with ARC, and just a warning without ARC. – Martin R Feb 21 '14 at 10:50

6 Answers6

2

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
miken32
  • 42,008
  • 16
  • 111
  • 154
Injectios
  • 2,777
  • 1
  • 30
  • 50
  • I think that the solution is to make a category class of Player. Se my answer below ;) – Ali Abbas Feb 21 '14 at 11:06
  • Take a look on this discussion : http://stackoverflow.com/questions/522341/what-is-the-difference-between-inheritance-and-categories-in-objective-c – Ali Abbas Feb 21 '14 at 11:13
  • @ali59a there are certain circumstances where categories are a lot better over creating a whole do class but in this instance the creating a category just would help and wouldn't even make sense this instance definitely calls for inheritance. The idea is they have a base class of Player and they can create different types of Players from this base class for example ComputerPlayer, ConsolePlayer, SmartPlayer etc all having different methods and properties. If you just created a category every class would have all these methods and properties when they shouldn't. – Popeye Feb 21 '14 at 11:21
  • 1
    @Popeye I agree ! Technically this is not the right approach. Thank you for your comment – Ali Abbas Feb 21 '14 at 11:24
  • F.e let's imagine 3 types of players: fire, ice, water. Developer in some cases doesn't know which player is CURRENT. Which method then developer should execute? Base class has -execute method. Fire class has -executeFire method, water -executeWater method, sorry, but for me - that's a point – Injectios Feb 21 '14 at 11:26
  • @ali59a Your totally wrong category wouldn't work at all in this case, your approach of using categories is completely not the way to go and would be a very bad approach. This way is technically correct. It would either be through inheritance or introspection but not categories. – Popeye Feb 21 '14 at 11:29
  • @popeye Yes i see. In this case we have to use Inheritance instead of Category. I up vote the answer. – Ali Abbas Feb 21 '14 at 11:47
  • I was thinking more along the lines of if this was Java where if I had a class Animal and a class Dog that extended Animal I could declare a dog as follows: Animal dog = new Dog(); – perrigal Feb 21 '14 at 12:38
  • Yes I think this is the right way to go, marking correct. Thank you – perrigal Feb 21 '14 at 13:43
1

You can test, if it is a computer player

 if ([currentPlayer isKindOfClass:[ComputerPlayer class]])
     [(ComputerPlayer *)currentPlayer computerPlayerOnlyMessage];
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • +1 as straight forward and on very rare occasion I agree with you and on comments on other answers. – Popeye Feb 21 '14 at 10:54
  • that, of course, will kill warning. But is that correct approach/logic? – Injectios Feb 21 '14 at 11:07
  • @Injectios why wouldn't it be the correct approach/logical??? Please could you explain your comment. – Popeye Feb 21 '14 at 11:15
  • @Injectios: this is one tool for introspection inObjective-C/Cocoa. – vikingosegundo Feb 21 '14 at 11:18
  • actually, it was a question. Anyway, don't you think it could be just temporary solution to get rid of warning, BUT there is mistake in architecture? I agree that this way we avoid warning, but in my opinion, there are might be more such cases, and convert types everywhere is not good solution – Injectios Feb 21 '14 at 11:23
  • There is no mistake in the architecture: sometimes you have to ask, if a object is of a certain sub class – vikingosegundo Feb 21 '14 at 11:30
0

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;
Greg
  • 25,317
  • 6
  • 53
  • 62
  • and what if he wants to assign a human player to both player ivars? – vikingosegundo Feb 21 '14 at 10:49
  • Exactly it's depends what he wants. My solution will work for some cases and your will work for other. What if he needs to call this method many times? with your answer he needs to cast it many time and it produces much more code. It depends what works better for him. – Greg Feb 21 '14 at 10:52
  • while casts are not the most fanciest tools in the belt, they consume no run time, as the are evaluated to compile time. We can't see how apple implemented `isKindOfClass:`, but we can assume, that it is fast, as under the hood it must be some type checking. and anyway: [Premature optimization is the root of all evil.](http://en.wikiquote.org/wiki/Donald_Knuth) – vikingosegundo Feb 21 '14 at 11:29
0

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];
    }
}
Popeye
  • 11,839
  • 9
  • 58
  • 91
0

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

Antonio
  • 71,651
  • 11
  • 148
  • 165
0

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.

greymouser
  • 3,133
  • 19
  • 22