Why is a protocol hiding methods and properties from the super class? - ios

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"];
}

Related

iOS Class Method

#interface TestObj :NSObject
#property(copy, nonatomic)NSString *name;
#end
#implementation TestObj
- (void)testName{
NSLog(#"name:%#",self.name);
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id tclass =[TestObj class];
void * vid = &tclass;
[(__bridge id)vid testName];
}
#end
log :
name: <ViewController: 0x7ff3584b6580>
My understanding is vid is a pointer to the address of the TestObj class object, then why vid can be sent directly to the testName instance method?
Also, why the testName method calls the NSLog will output <ViewController: 0x7ff3584b6580>instead of nil?
Thank.
I think you are basically getting lucky that you aren't crashing with that code.
First, class methods start with a + not a - -- so that is an instance method you are implementing.
#interface TestObj :NSObject
#property(copy, nonatomic)NSString *name;
#end
#implementation TestObj
+ (void)testName{
NSLog(#"name:%#", #"TestObj"); // cannot reference ivars or properties in class method
}
#end
...
Class classObject = [TestObj class];
[classObject testName];
You don't want to take pointers to class objects (or instances either). The Objective-C runtime will dereference the pointer you give it, in order to find the "isa" instance variable, which will be the Class it belongs to. The "isa" of a class object is the "meta-class", which contains the list of class methods, so that is how class method lookup works. In your example, it would dereference the pointer and find the TestObj class, meaning it is thinking it is calling a method on a TestObj instance when it's not -- you have not allocated one, but it's really just a garbage pointer. But, it will still (by complete luck) get into your instance method implementation, but "self" isn't really a valid instance. However, it looks like whatever the value is, just so happens to respond to a -name method and return either an NSString with that value or the UIViewController instance itself. Or maybe it's trying to deference the instance variable based on the garbage pointer and ending up with a pointer to the ViewController instance by happenstance. I think that would try to call a -name method though.
Anyways, tl;dr -- your code is quite wrong (don't use & pointers of classes or instances) and you are just getting lucky you aren't crashing. The (bridge id) cast is hiding the warning which was trying to help you.

How To Solve No Type or Protocol Named Error In Xcode 7?

I m trying to passing values from second class to first class for that I am using protocol and delegate process. Whenever I run my program I am facing below Issue.
No Type or Protocol Named 'locateMeDelegate'
Viewcontroller A .h
#interface first : UIViewController < locateMeDelegate > { }
In my case the issue was caused by importing the delegate's header file to the delegator's class .h file. This seems to create a sort of vicious circle. As soon as I deleted the import statement of the delegate's header from the delegator's .h file, the error went away.
Tipically, if you intend your protocol to be used by other classes you must declare it in the header file like this:
// MyClass.h
#protocol MyProtocol;
#interface MyClass : NSObject
#end
#protocol MyProtocol
- (void) doSomething: (MyClass*) m;
#end
After you declare it, you should implement the methods of the protocol in the implementation file, which should conform to the protocol like this:
// MyClass.m
#implementation MyClass <MyProtocol>
pragma mark - MyProtocol methods
- (void) doSomething: (MyClass *)m {
// method body
}
#end
After these two steps you're ready to use you protocol in any class you desire. For example, let's say we want to pass data to MyClass from other class (e.g. OtherClass.h). You should declare in OtherClass.h a property so that we can refer to MyClass and execute the protocol. Something like this:
// OtherClass.h
#import MyClass.h
#interface OtherClass : NSObject
#property (weak) id<MyProtocol> delegate;
#end
You don't forget to import the header file where you declared your protocol, otherwise Xcode will prompt No Type or protocol named "MyProtocol"
id<MyProtocol> delegate; means you can set as the delegate of OtherClass any object (id) that conforms to the MyProtocol protocol (<MyProtocol>)
Now you can create an OtherClass object from MyClass and set its delegate property to self. Like this:
// MyClass.m
- (void)viewDidLoad() {
OtherClass *otherClass = [[OtherClass alloc] init];
otherClass.delegate = self;
}
It's possible to set the delegate to self because the delegate can be any object and MyClass conforms to MyProtocol.
I hope this can help. If you want to know more about protocols you can refer to this two websites:
Working with Protocols - Apple Documentation
Ry's Objective-C Tutorial (This one is easy to pick up)
I also faced the same issue and it seems the error is from Xcode itself. Please Try running on Physical device. This would solve the issue faced.

Declared property is a subclass, but my view controller thinks its its superclass

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!

Objective C Polymorphism for a Game Class

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.

How to resolve "no known instance method for selector 'performSelector:withObject:afterDelay:'" when migrating to ARC?

The ARC migration tool is refusing to accept this code prior to starting with migration:
[self.delegate performSelector:#selector(overlayDismissed:) withObject:self afterDelay:0];
The delegate is forced to implement this method with a protocol, and it should work fine:
#protocol OverlayDelegate <NSObject>
- (void)overlayDismissed:(Overlay*)overlay;
#end
#interface Overlay : UIImageView {
id<OverlayDelegate> delegate;
}
#property (nonatomic, assign) id<OverlayDelegate> delegate;
What's wrong with ARC? Why is it telling me that there is "no known instance method for selector 'performSelector:withObject:afterDelay:'?
ARC isn't causing this - it is is merely exposing it. That method is defined on NSObject - but id works for more than just NSObject (so you have to be more specific than just 'id'). Change your code to this:
#interface Overlay : UIImageView {
NSObject<OverlayDelegate> *delegate;
}
#property (nonatomic, assign) NSObject<OverlayDelegate> *delegate;
Simple, your object is of type id and conforms to the NSObject protocol. However, this protocol doesn't declare performSelector:withObject:afterDelay:, so ARC doesn't know what the method is doing and if it must retain anything. Either use an NSObject or cast it prior to making the method call.
I've figured out that casting the delegate to NSObject* solves the problem:
[self.delegate performSelector:#selector(overlayDismissed:) withObject:self afterDelay:0];
For some weird reason autocompletion did not even come up with -performSelector:withObject:afterDelay: so I had to type it manually. Instead, it offered only -performSelector: and -performSelector:withObject:withObject:
My guess is that it's just stupid to use id as the type for delegates in Objective-C, and I never really knew why everyone including myself is doing that rather than just defining it as NSObject. However, my protocol even told that whoever conforms to that protocol also has to conform to the NSObject protocol by doing this: OverlayDelegate <NSObject> - and still, the compiler didn't get it.
So for now I'm satisfied it works with the cast, but it feels like eating old fish.
I met error:
No known class method for selector conformsToProtocol:
The reason is that : file name is not equal to the class name with #interface and #implementation.

Resources