Struggling with SKScene inheritance - objective-c

I have a subclass of SKScene called SPGamePlayScene. It does a bunch of stuff. I need to make another scene that does the same stuff as SPGamePlayScene, but a little more/little differently, so I thought I would subclass my SPGamePlayScene for this new scene, SPPracticeScene. however im having issues with my class level instantiation methods.
below is the method of SPGamePlayScene:
//SPGamePlayScene.m
+ (instancetype)sceneWithSize:(CGSize)size colored:(BOOL)colored {
SPGamePlayScene *gamePlayScene = [SPGamePlayScene sceneWithSize:size];
gamePlayScene.colored = colored;
gamePlayScene.backgroundColor = [SKColor whiteColor];
return gamePlayScene;
}
this works fine. However in my subclass of SKGamePlayScene, SPPracticeScene, I need to create an SPPracticeScene object using its super class's sceneWithSize colored method.
//SPPracticeScene.m which inherits from SPGamePlayScene
+ (instancetype)sceneWithSize:(CGSize)size {
//this keeps giving me a SPGamePlayScene but I need it to be a SPPracticeScene
SPPracticeScene *practiceScene = (SPPracticeScene *)[self sceneWithSize:size colored:NO];
//this line throws an exception because practiceHud is not a property of SPGamePlayScene
practiceScene.practiceHud = [SPPracticeHud practiceHudAtPosition:CGPointMake(0, practiceScene.frame.size.height - 20) inClef:[SPGameState sharedInstance].clef inFrame:practiceScene.frame];
[practiceScene addChild:practiceScene.practiceHud];
return practiceScene;
}
I know that this returns an SPGamePlayScene so I tried casting it with no luck. The line that sets the practiceHud property causes a crash since SPGamePlayScene does not have that property (even though its supposed to be an SPPracticeScene. This is my first foray into custom subclasses/inheritance so im probably misunderstanding something about the way things need to be done/what types need to be returned etc. How can I make this work?

I ended up just overriding the initWithSize method and using an instance level initializer instead of a class level one. using an init method returning an id seemed to do the trick.
//SPGamePlayScene.m
(id)initWithSize:(CGSize)size colored:(BOOL)colored {
if (self = [super initWithSize:size]) {
//some custom code here
}
return self;
}
//SPPracticeScene.m which inherits from SPGamePlayScene
- (id)initWithSize:(CGSize)size {
//init it using the SPGamePlayScene custom initializer
if (self = [super initWithSize:size colored:NO]) {
//some custom code here
}
return self;
}
using the above code let me use the custom initializer for SPPracticeScenes super class, but add additional functionality to it that I needed, while making sure the object returned was of the correct type. I would love to know how I could have made it work with my class level initializers though.

Related

Besides syntax, is there any difference between writing an initialization method as an instance or class method?

Here is initialization as a class method:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = nil;
result = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if ( result )
{
result.imageDocument = imageDocument;
}
return result;
}
And here is an initialization as an instance method:
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
As far as I can tell, the only difference is that the sender doesn't need to call alloc when using a class initializer
But is there any other reason?
Also, a side question is there a name for a initializer that is a class method?
Like in NSColor
+ (NSColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_MAC(10_9);
With manual reference counting (MRC), the difference was the factory method typically returned an autoreleased instance while the instance method returned an instance that the caller now owned.
With automatic reference counting (ARC), that difference is now hidden from the calling code.
So the only difference is convenience for the caller as well as slightly more work for the author of the class to write the optional factory method.
BTW - your instance method initializer is incorrect. It should be more like this (not the proper way to set self):
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [super initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
And given this, your factory method should really be:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = [[[self class] alloc] initWithDocument:imageDocument];
return result;
}
Yes there is a major difference. If you implement a factory method (class method) you could for example choose to return an already existing instance of your object (e.g. from some sort of cache) instead of creating a new instance. Imagine you have a class Country which is expensive to initialize. Hence, write the following factory method that looks up the cache first and only if it does not find the country it creates a new object:
+(instancetype) countryForCountryCode: (NSString *)countryCode
{
Country *country = ... // ask our "CountryCache" if we already have a cached instance of the country
if (!country) {
country = [[Country alloc] init];
// Here you would also set up the new Country object, or even write a "private" initializer
// You would also add the new instance to the cache here
}
return country;
}
On the other hand, when you choose a "conventional" initializer the caller will always have created a fresh instance via alloc before your initializer gets called, and you'll have no way of returning a cached object instead.
Lastly I just remembered that I personally use factory methods whenever I deal with persistent objects (if not using CoreData). So, if I want to get a new object from the database I usually implement a factory method called "load". And to actually create a new record in the database I would implement another factory method named "create". If you would use initializers here it would become very confusing.

Passing Level parameter when transition to next Scene?

I'm trying to figure out the best way to pass a level parameter between Scenes using Spritebuilder and Cocos2D in Xcode.
I'm using the standard code below to transition between scenes.
[[CCDirector sharedDirector] replaceScene:[CCBReader loadAsScene:#"Gameplay"]];
Any help will be much appreciated.
Assuming that the Gameplay.ccb has a GameplayClass assigned as its custom class, and that class has a property named currentLevel, you can access the instance and assign the level as follows:
CCScene* theScene = [CCBReader loadAsScene:#"Gameplay"];
GameplayClass* game = (GameplayClass*)theScene.children.firstObject;
game.currentLevel = 3;
[[CCDirector sharedDirector] replaceScene:theScene];
Note that by the time currentLevel is assigned the GameplayClass will have already run its init and didLoadFromCCB methods since that happens during loadAsScene. If you need further init processing override onEnter in GameplayClass:
-(void) onEnter
{
[super onEnter]; // must call super
switch (self.currentLevel)
{
// other switches omitted...
case 3:
// your level 3 code here
break;
}
}
In my game (made with Cocos2d 2.0 + CocosBuilder) I added a class method loadWithLevelID:levelID to the class GameObjectLayer that manages the gameplay elements:
#implementation GameObjectLayer {
G1LevelID * _levelID;
}
// load a Luminetic Land game object layer
+ (instancetype)loadWithLevelID:(G1LevelID*)levelID {
NSString * levelFileName = ... builds levelFileName from levelID;
GameObjectLayer * gol = (GameObjectLayer*) [CCBReader nodeGraphFromFile:levelFileName];
[gol setLevelID:levelID];
return gol;
}
- (void)setLevelID:(G1LevelID*)levelID {
_levelID = levelID;
}
So now I can create a GameObjectLayer typing
GameObjectLayer * gol = [GameObjectLayer loadWithLevelID:levelID];
In general, adding a "load" method to the classes that map ccbi files offers the following benefits:
Only the class knows the name of its related ccbi file.
The ccbi file to create a given class is referenced only once in the entire project.
All the logic to set up an object (e.g. of type G1GameObjectLayer) is inside the object itself.
I think you can follow a similar approach with your CCScene subclass.

Designated initializer and calling it

I have general question about designated initializer. I have a some class and from there i want to call a initializer, but before i started to fill my #properties with passing data i want to make data default. For example:
-(id)initWithDefault:(NSDictionary*)defaultTemplate
{
self = [super init];
_fontColor = [defaultTemplate objectForKey:#"color"];
_fontSize = [[defaultTemplate objectForKey:#"size"] intValue];
return self;
}
-(id)initWithTemplate:(NSDictionary*)template
{
self = [self initWithDefault:myDefaultTemplate];
//now i doing something with my template
return self;
}
Is this is a way to prevent null #properties? It this a correct use of designated initializer? Of course you can assume that myDefaultTemplates is not null, and has not null object in keys.
This seems fine with me. I would use the safe way (presented below), but otherwise your code is fine.
-(id)initWithTemplate:(NSDictionary*)template
{
if(self = [self initWithDefault:myDefaultTemplate]) {
//now i doing something with my template
}
return self;
}
Your implementation is perfectly fine, given the fact that _fontColor and _fontSize variables are your local variables of properties.
adig's suggestion is just an enhancement on what you already have implemented. This check takes care of the situation, when your object does not get allocated due to any reason.

MacRuby custom initializers

Just discovered MacRuby this afternoon; man is that ever COOL! However, I've run into some difficulties while attempting to extend an old project with some MacRuby-fu. Here's the deal:
So I have a superclass in Objective-C that looks like this:
#implementation Foo
- (id) init {
if (self = [super init]) {
//Do nothing, don't have enough data...
}
return self;
}
- (id) initWithName:(NSString*)n andLocation:(NSString*)loc andSomethingElse:(Bar*)b {
if (self = [super init]) {
//Set a LOT of internal state...
}
return self;
}
#end
So, in a ruby file, we'll call it Mung.rb that looks like this:
class Mung < Foo
def initWithSomethingElse(else, andEvenMore:more)
super.initWithName("Moop", andLocation:else, andSomethingElse:more.addVal(42))
self
end
end
When I go to instantiate a Mung (myObj = Mung.alloc.initWithSomethingElse("Boo", andEvenMore:"US"), the runtime explodes telling me there is no method defined in Mung's super called 'initWithSomethingElse'. This is true, but it means that I cannot define custom initializers in ruby files. My current workaround is to provide a homogenous initializer that takes a hash, and then the individual subclasses parse the hash as needed. I don't like this approach and would like: A. An explanation of why 'initWithSomethingElse' is ever called on super, and B. If no direct solution can be applied, an alternative workaround. Thanks guys!
You can't call the super version of a different method from a method in MacRuby. The super keyword respects the Ruby semantics and will only dispatch a call to the super version of the current method.
In your case, you may want to send initWithName:andLocation:andSomethingElse: to self directly, and if needed, you can re-define this selector on the class and call super appropriately.

Pull-up refactoring, Objective-C

I have two similar classes, MultiSlotBlock and SingleSlotBlock. They have started to share a lot of common code so I have decided to do some refactoring and pull some of the methods up to a new superclass, let's call it Block.
Now one of the methods that I pull up, simplified for the example, looks like this:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
[self doAUniqueBehaviour];
}
The problem here is that [self doAUniqueBehaviour] is showing a warning because of course my superclass doesn't implement this method anywhere.
The two solutions I thought of don't sound great to me. One is to use a protocol (the way I am currently doing it) like so:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
if ([self conformsToProtocol:#protocol(UniqueBehaviourProtocol)])
{
id<UniqueBehaviourProtocol> block = (id<UniqueBehaviourProtocol>)self;
[block doAUniqueBehaviour];
}
}
The other is to have a blank method body in my superclass (in this case there would be a lot) and just return doesNotRespondToSelector.
Something is tingling at the back of my mind that I should be using the Strategy Pattern, but I might be way off, and I haven't thought through how that would be implemented.
Any ideas? Thanks.
EDIT: I know for a fact that doAUniqueBehaviour will be implemented in all subclasses, it is just the implementation that will differ.
The superclass should not know about its subclasses. You should implement the
- (void)doACommonBehaviour method in every subclass and there:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour];
}
EDIT - clarification:
If all the subclasses are going to implement -doAUniqueBehaviour then it should be implemented in the superclass (even empty) and each subclass will override it to its needs.
If subclass1 implements -doAUniqueBehaviour1, subclass2 implements -doAUniqueBehaviour2 etc then do what I propose above; eg. in subclass1:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour1];
}
There is not such concept as abstract class in Objective-C. In order to avoid the warning, you have to provide a default implementation in your base class. Usually, this implementation will throw a doesNotRespondToSelector error at runtime:
- (id)someMethod:(SomeObject*)blah
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Note: the _cmd argument is the invoked selector.
#Dimitri's suggestion will work, but instead of forcing each subclass to implement the same method, you can declare it once in Block, and just above that method (in the implementation file, not header) declare the unique method like so:
- (void) doUniqueBehaviour { }
- (void) doCommonBehaviour {
// any common code you need
[self doUniqueBehaviour];
}
This will prevent any compiler warnings, and you can override -doUniqueBehaviour in subclasses as you like. It also avoids code duplication and reduces the potential for changing the code in one subclass but not another. Plus, you don't need a separate protocol, and dynamic typing is preserved.