My situation is that I have a few different mini-games in my app. The games are listed in a table view, and when one of them is selected I create the corresponding game object. I need the game objects to have a common parent class, so that I can do common things such as pausing and resuming.
I have a generic GameScene class which has a generic GameLayer property:
#interface GameScene : CCScene {
}
#property (nonatomic, strong) GameLayer* gameLayer;
However each child class of GameScene has it's own type of GameLayer. So for example the SuperMarioBros_GameScene has a SuperMarioBros_GameLayer. The problems arise when I initialize the SuperMarioBros_GameScene:
-(id) init
{
if( (self = [super init])) {
self.gameLayer = (SuperMarioBros_GameLayer*)[SuperMarioBros_GameLayer node];
[self addChild: self.gameLayer];
SuperMarioBros_InputLayer *inputLayer = [SuperMarioBros_InputLayer node];
inputLayer.delegate = self.gameLayer; //ERROR (see below)
[self addChild:inputLayer z:1 tag:2];
}
return self;
}
In general each game will also have an input layer, which will send UI messages to the gameLayer. I get this error:
Assigning to 'id <SuperMarioBros_FireDelegate> from incompatible type GameLayer*
I understand this is happening because SuperMarioBros_GameLayer implements <SuperMarioBros_FireDelegate>, but GameLayer doesn't.
So my question is how do I make the GameScene class hold a polymorphic property that can be any subclass of GameLayer? Keeping in mind that each type of GameLayer may implement different protocols based on their corresponding input layers? Or maybe there's a better way to go about this?
Again, the end goal is to be able to make a call from the main game controller such as
[currentGameScene pause];
which will in turn do something like:
[self.gameLayer pauseGameLoop];
In the implementation file of the SuperMarioBros_GameScene class, you can override the property declaration of the gameLayer property in a class extension:
#interface SuperMarioBros_GameScene ()
#property (nonatomic, strong) SuperMarioBros_GameLayer *gameLayer;
#end
Then you should not get a compiler error anymore.
This will not create new getter or setter functions (these are still called in the superclass), and does not introduce a new instance variable as backing store. It only tells the compiler that self.gameLayer is an instance of the subclass in this implementation file.
Related
Consider this class: CAEmitterLayer. This class responds to the property name.
Now I have created a CAEmitterLayer called MyEmitter and this class conforms to a protocol I have created called MyProtocol.
MyEmitter class declaration is like this:
HEADER
#import "MyProtocol.h"
#interface MyEmitter : CAEmitterLayer <MyProtocol>
#end
IMPLEMENTATION
#import "MyEmitter.h"
#implementation MyEmitter
#synthesize internalString = _internalString;
#end
and the protocol is just this:
#protocol MyProtocol <NSObject>
#property (nonatomic, strong) NSString * internalString;
#end
This is the problem. If I create a new object like this
MyEmitter *obj = [[MyEmitter alloc] init];
and try to use the name property, xcode complains with no known instance method for selector 'name'
In fact I cannot access any property from the class CAEmitterLayer even MyEmitter being a subclass of that class.
I am trying to use it like this:
for (id <MyProtocol> node in nodes) {
[node setName:#"ddd"]; // error here
}
Apparently the protocol is hiding everything from the super class. Why is that and how do I solve?
NOTE: I had to add that synthesize line to the class, or xcode would not stop complaining.
The static type of node is id <MyProtocol>. The compiler rightfully says that this type doesn't declare a setName: method.
Just switch to MyEmitter * and it should work.
To expand upon the other answer, you are receiving a compile time error because the compiler has to work from what you have told it.
Now, you know (or at least hope) that node is going to be an instance of MyEmitter at run-time, but the compiler doesn't because you have told it that node is an id <MyProtocol>.
If you could get your program to run anyway then [node setName:] would work because objective-C finds the right selector at run time.
Similarly if you told the compiler that node was an instance of MyEmitter but at run time it was a different object class (due to an error somewhere else in your code) then it would compile but potentially throw an exception at run time.
So you could say -
for (MyEmitter *node in nodes) {
[node setName:#"ddd"];
}
Or, if you don't need to do anything that is specific to your subclass you could even say
for (CAEmitterLayer *node in nodes) {
[node setName:#"ddd"];
}
Background info
I have a view controller that is running a cocos2d scene (so I can put UIkit objects on top of the scene).
My app is crashing with the following error:
2014-10-25 11:20:04.426 AppName[24166:992733] -[CCScene avatar]: unrecognized selector sent to instance 0x7c5a3270
2014-10-25 11:20:04.428 AppName[24166:992733] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CCScene avatar]: unrecognized selector sent to instance 0x7c5a3270'
I know that the reason the app is crashing is because its trying to call the getter method avatar on a CCScene, instead of the CHCreateAvatarScene which is a subclass of CCScene. If I look in the debugger, the VC thinks that my currentScene property is of type CCScene, not CHCreateAvatarScene so obviously it can't find the Avatar property. Am I declaring it wrong? I can't figure out why this is the case. I'm also a bit of a programming newbie, just FYI. Its probably an obvious mistake.
CHCreateAvatarViewController.h
#import "CHCreateAvatarViewController.h"
#import "CHCreateAvatar.h"
#import "CHAvatarAttribute.h"
#import "CHAvatarAttributeOption.h"
#import "CHAttributeData.h"
#import "CHCreateAvatarScene.h"
#import "CHAttachment.h"
#interface CHCreateAvatarViewController () <CocosViewControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
...
#property (strong, nonatomic) CHCreateAvatarScene *currentScene;
...
#end
#implementation CHCreateAvatarViewController
...
#pragma mark - CocosViewControllerDelegate
-(CCScene *)cocosViewControllerSceneToRun:(CocosViewController *)cocosViewController
{
//This will load the Spritebuilder file which is a loaded as a CCScene.
// I then told it to expect a CHCreateAvatarScene because otherwise I was getting an 'invalid pointer' error.
// I also tried changing the return type of this method to CHCreateAvatarScene to see if that would have any effect but it didn't, so I changed it back.
self.currentScene = (CHCreateAvatarScene *)[CCBReader loadAsScene:#"CreateAvatarScene"];
[self setupSpritesWithAttachments:self.factory.attachments];
return self.currentScene;
}
...
-(void)setupSpritesWithAttachments:(NSMutableArray *)attachments
{
int i = 0;
//This is where its crashing
for (CCSprite __strong *sprite in self.currentScene.avatar.attachmentSprites) {
CHAttachment *attachment = attachments[i];
sprite.texture = attachment.texture;
i++;
}
}
...
CHCreateAvatarScene
// .h
#import "CCScene.h"
#import "CHAvatar.h"
#interface CHCreateAvatarScene : CCScene
#property (strong, nonatomic) CHAvatar *avatar;
#end
//.m
#import "CHCreateAvatarScene.h"
#implementation CHCreateAvatarScene {
CCNode *avatarNode;
}
-(void)didLoadFromCCB
{
self.avatar = (CHAvatar *)[CCBReader load:#"Avatar"];
[avatarNode addChild:self.avatar];
}
CHAvatar (I don't think its relevant, but included it just in case)
//.h
#import "CCNode.h"
#interface CHAvatar : CCNode
#property (strong, nonatomic) NSMutableArray *attachmentSprites;
#end
//.m
#import "CHAvatar.h"
#implementation CHAvatar {
CCSprite *_shoulders;
CCSprite *_neck;
CCSprite *_head;
}
//Have left off the head for now just to get this working.
-(void)didLoadFromCCB
{
self.attachmentSprites = [#[_shoulders, _neck] mutableCopy];
}
#end
Thanks in advance for any help with this!
The declared type of a variable expresses an intention to the compiler. "I intend to store this type of thing in this storage." The compiler will set aside the proper amount of storage for that type of variable (in this case, a pointer) and it will try to warn about cases where the code is clearly trying to put the wrong type of thing into the variable. But it can only do static checks at compile time. It doesn't put in dynamic checks at run time. It doesn't check what the code is actually doing.
Importantly, the declared type of a pointer variable does not control the actual type of thing being pointed to by any pointer stored into it. Just because you have declared your intent, that doesn't mean your actions (i.e. your code) match that intent.
In your case, the expression [CCBReader loadAsScene:#"CreateAvatarScene"] is actually returning an instance of CCScene, not an instance of CHCreateAvatarScene. You have a type cast to tell the compiler to treat the return value as though it were a pointer to CHCreateAvatarScene. That silences the compiler from complaining, but doesn't actually change the nature of the object the pointer points to.
You wrote in a couple of places that "the view controller thinks" the object is of the wrong class and so can't find the property. This is exactly backward. The code is written to "think" that the object is always of the type CHCreateAvatarScene but it really isn't. The view controller doesn't have to "find" the property. It just acts as though the property exists by calling the getter method. It is the object that has received that message that doesn't know how to respond to it because it's not actually a CHCreateAvatarScene. It's a CCScene object.
The debugger and the error message are both correct about the actual type of the object.
The real question is how +[CCBReader loadAsScene:] works. Why would you expect it to return an instance of CHCreateAvatarScene? Why is it behaving differently than you expect and returning an instance of CCScene?
A friend helped me figure it out, so I'm posting the answer.
Basically, I was mixing up concepts of scenes and nodes in Cocos2d. Here was how I fixed it:
Change CHCreateAvatarViewController's property to type CCScene *currentScene and remove (* CHCreateAvatarScene) casting in the cocosViewControllerSceneToRun: method. In fact, I could probably remove this property all together after this solution is complete.
Rename CHCreateAvatarScene to CHCreateAvatarNode (I was getting mixed up with the concepts of scenes and nodes, so this helped). Change it to be a subclass of CCNode, not CCScene.
Add a CCNode *avatarNode property to the vc. In CCBReaderDidLoad: in the vc, add self.avatarNode = [[CHCreateAvatarNode alloc] init];
In the for loop where the app originally crashed change self.currentScene.avatar.attachmentSprites to self.avatarNode.avatar.attachmentSprites
And voila!
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.
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
You can test, if it is a computer player
if ([currentPlayer isKindOfClass:[ComputerPlayer class]])
[(ComputerPlayer *)currentPlayer computerPlayerOnlyMessage];
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;
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];
}
}
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:
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)
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
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.
Is it possible to make an instance of a class and then send/recive data from every scene in a game?
Ex: Let say you make a RPG game, and you want to create a "Party" with information like Party Leader, members etc... and then you want different battle scenes to use the data from the very same instance?
If possible, then how?
/Daniel
First option: use a singleton.
Second option: pass the instance as a parameter to custom init method of a scene:
#interface GameScene1()
#property Party *party;
#end
#implementation GameScene1
-(id)initWithSize:(CGSize)size party:(Party*)party {
if (self = [super initWithSize:size]) {
self.party = party;
.....
}
}
#end
I have a few different viewControllers that need to inherit the same properties, but aren't the same type of viewController. For example, one VC is a regular UIViewController, whereas another one is a UISplitViewController. Is there any way for me to efficiently use inheritance to make sure they all have these certain properties? Or do I just need to give each one their own separate declarations?
You can achieve what you want using a category on UIViewController. You can implement the properties in the category using associated objects.
See Objective-C: Property / instance variable in category for more details.
You could add a category to UIViewController. Since UISplitViewController inherits from UIViewController, it will have all properties and methods as defined in the category as well. However, categories have two limitations:
You can't add backing instance variables. You can create properties, but they can't have instance variables backing them. That means that if you are overriding the getter (and setter, if readwrite), so that it reads (or writes) an already existing property in some way, you're good. If not, you can look at associated objects.
Overriding methods in a category is a no-no. While nothing stops you from doing it, you have undefined behavior if another category overrides that method too. You just don't know which method will get executed. If you need to override methods, subclassing UIViewController would be better. However, UISplitViewController will then not know about these properties, unless you subclass it as well and add those same properties (in which case you're maintaining these properties twice).
I'm not sure what exactly do you need. If you don't want to (or can't) use common superclass with public properties, you can always write protocol. Only difference is that, protocol don't give you common implementation, but force you to write one (so you can be sure it is there, as you asked for).
Why not set up inheritance using a shared base class and set those shared properties in the init?
//MyBaseVC.h
#interface MyBaseVC : UIViewController
#property (nonatomic, strong) NSString *myString;
#end
//VC1.h
#interface VC1 : MyBaseVC
#end
//VC2.h
#interface VC2 : MyBaseVC
#end
-----
//(MyBaseVC.m)
-(id) init {
self = [super init];
if(self){
self.myString = #"Hello world!";
}
return self;
}
// VC1.m
-(id) init {
self = [super init];
NSLog(#"%#", self.myString); // "Hello world!"
return self;
}
// VC2.m
-(id) init {
self = [super init];
NSLog(#"%#", self.myString); // "Hello world!"
return self;
}
At that point, you can directly refer to the property on the subclassed objects:
NSLog(#"%#",myVc1.myString); //"Hello world!"
Otherwise, when you reference the VCs in a more generic fashion, you can always refer to their super class (MyBaseVC) - for example, if you need to pass them as a method parameter.
//-(void)doSomethingWithVC:(MyBaseVC *)vc;
[someObj doSomethingWithVc: vc1];