iOS Class Method - ios

#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.

Related

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

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

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!

Deallocation of object

I have an object which inherits from the MKPlacemark class of MapKit. I have a method launch during viewDidLoad of my ViewController that creates such object (alloc + init) and pass it to a MapView as follows
[self.mapView addAnnotation:<my instance of my class inheriting MKPlacemark>]
However, when I launch my program, I get the following error message:
An instance 0x9a5d650 of class <name of my class> was deallocated while key value
observers were still registered with it. Observation info was leaked, and may even
become mistakenly attached to some other object. Set a breakpoint on
NSKVODeallocateBreak to stop here in the debugger.
Note that I use ARC. Can anyone tell me how can I avoid such deallocation?
Thanks !
EDIT: My problem is not the warning in itself, it is that I do not want this object to be deallocate at that moment...
EDIT2: The code of the class is the following
The .h file looks like this
#interface OPTCreatureMark : MyMark
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
-(id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
and the .m like that
#implementation MyMark
#synthesize coordinate;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate_ {
if (self = [super initWithCoordinate:coordinate_ addressDictionary:nil]) {
self.coordinate=coordinate_;
return self;
} else {
return nil;
}
}
#end
If you are indeed using KVO, it sounds like you need to remove the observer in your object's dealloc method like so:
[self removeObserver:self.myDelegate forKeyPath:#"zoom"];
Otherwise messages could be sent to a deallocated instance of your class (which can no longer respond because it's been deallocated), thus causing an exception.

Proper way to use instance variables/property/synthetize with ARC [duplicate]

This question already has answers here:
Declaration/definition of variables locations in ObjectiveC?
(4 answers)
Closed 9 years ago.
What is the proper way to work with instance variables (declared on interface), their #property and #synthesize, when working in ARC project? What I now do is following:
SomeClass.h:
#interface SomeClass : NSObject {
NSString *someString;
}
#property(nonatomic, copy) NSString* someString;
and SomeClass.m:
#implementation SomeClass
#synthesize someString;
- (void)someMethod {
self.someString = #"Foobar";
}
The thing is that there are other approaches that works, like using just the #property:
SomeClass.h:
#interface SomeClass : NSObject
#property(nonatomic, copy) NSString* someString;
Accessing the someString without self:
SomeClass.m:
#implementation SomeClass
#synthesize someString;
- (void)someMethod {
someString = #"Foobar";
}
etc. I'm new to Objective-c, I'm used to Java. What is the proper way to work with attributes then? I understand that special cases will have special behavior, but what is the best approach in general? (by general I mean I want to access the variable from the class itself and from "outside" and I want ARC to still work correctly, eg. I don't have to worry about memory leaks)
For simple properties, you don't need the instance variable declaration or the #synthesize. The clang compiler will generate both for you by default. So you could write this in the header:
#interface SomeClass : NSObject
#property (nonatomic, copy) NSString *someString;
#end
And the implementation:
#implementation SomeClass
- (void)someMethod {
self.someString = #"Foobar";
}
#end
Avoid direct instance variable access unless you are in the -init method or overriding the setter. Everywhere else you should use the dot syntax (self.someString). If you do need access to the instance variable, the default synthesize will create an underscore-prefixed ivar, e.g. _someString.
Note that for classes with mutable versions like NSString/NSMutableString and NSArray/NSMutableArray the standard practice is to use a copy property. If you use strong on a string or array, the caller might pass in a mutable version and then mutate it from under you, causing hard-to-find bugs.
Check out this SO post for information about ARC.
(Edited) The "strong" attribute tells ARC to keep an object around until the object with the property is deallocated. You do need the "copy" attribute because an NSString property could have been passed in as an NSMutableString. The "copy" guarantees that the original object will be kept around. Again, I apologize for the incorrect/misleading information I originally had here.
The reason you can access the instance variable someString as well as the property self.someString is that the #synthesize someString line creates an instance variable for the property and creates methods for getting and setting the value of it. However, it is recommended that you use the property instead of directly using the instance variable because by using the instance variable, you cannot let the parent object know that you've changed one of its properties.

iOS : Other alternative to instance variable?

I have a project which others have written and I have taken over it, hoping to make the app better.
I encountered one problem:
From one class:
I write _customclass.variable. CustomClass is another class and variable is a property and is of int type. And I get value of the variable in this class, but when I change it to self.customclass.variable, I always get 0. Is there other alternative ways to get value from other class?
(a)
#property (readwrite)int boxSpacing;
(b)
#synthesize boxSpacing;
(c)
- (id)initWithCoder:(NSCoder *)aDecoder {
self.boxSpacing = 10;
}
You asked:
Is there other alternative ways to get value from other class?
The short answer is that using the "getter" is the customary way to get a value from another class. But looking at your problem (admitted, not having enough source code to properly diagnose your issue), I'm guessing that the issue rests in the use of instance variables. But more on that later.
First, let's look at the proper use of declared properties and their instance variables and their accessor methods (the getters and setters). Generally you should set properties using these accessor methods. You can, though, use access a variable from within a class using either the instance variable (and you should not use accessor methods in initializer and dealloc methods). And when using the getter and setter, you can choose whether to use the method invocation (e.g. "[self customObject]") or the dot notation (e.g. "self.customObject").
Let's look at an example. Let's assume you have some simple CustomClass:
#interface CustomClass : NSObject
{
// you don't need to declare the instance variable
//int _boxSpacing;
}
#property (nonatomic) int boxSpacing;
#end
#implementation CustomClass
// In Xcode 4.4 and later, the synthesize statement is optional, and if you
// omit it, it will synthesize the instance variable like this, with the
// leading underscore. While you don't need to use an underscore in your
// instance variable, it has become convention in iOS development and it's
// a good technique to minimize chances that you accidentally use the instance
// variable when you actual intended to use the property's accessor methods
// (the getter and setter).
#synthesize boxSpacing = _boxSpacing;
#end
Now, let's assume that you're going to use this CustomClass from within, for example, your view controller. So, first you declare and instance of this CustomClass:
#interface MyViewController : UIViewController
{
// you do not need this instance variable declaration
// the #synthesize statement will take care of this for you
// CustomClass *_customObject;
}
#property (nonatomic, strong) CustomClass *customObject;
#end
And then let's demonstrate how to use the value property of the CustomClass object customObject from within your view controller:
#implementation MyViewController
// Again, in Xcode 4.4 and later, the synthesize statement is optional, and if you
// omit it, it will synthesize the instance variable like this, with the
// leading underscore
#synthesize customObject = _customObject;
- (void)customClassTest
{
// initialize the object
self.customObject = [[CustomClass alloc] init];
// set the property
self.customObject.boxSpacing = 1;
// finally, let's demonstrate three ways to retrieve the value
NSLog(#"%d", self.customObject.boxSpacing);
NSLog(#"%d", [[self customObject] boxSpacing]);
NSLog(#"%d", _customObject.boxSpacing);
// while we're at it, let's demonstrate other ways to set the property
_customObject.boxSpacing = 2;
// or
[[self customObject] setBoxSpacing:3];
}
Ok, so let's get back to your problem. You say:
I write _customclass.variable. CustomClass is another class and variable is a property and is of int type. And I get value of the variable in this class, but when I change it to self.customclass.variable, I always get 0.
Ok, this can be caused by a couple of different problems, but the most common problem I see is confusion between explicitly declared instance variables and the instance variables created behind the scenes by the #synthesize statement. This is why I always advise that people not explicitly define the instance variables for their declared properties, but rather let the #synthesize statement do that automatically. That way I can't have the sort of problem I'm about to demonstrate.
Consider this innocuous (though incorrect) example:
#interface MyViewController : UIViewController
{
CustomClass *_customObject;
}
#property (nonatomic, strong) CustomClass *customObject;
#end
#implementation MyViewController
#synthesize customObject;
- (void)customClassTestError
{
// initialize the object
self.customObject = [[CustomClass alloc] init];
// this works
self.customObject.boxSpacing = 1;
// this doesn't!
_customObject.boxSpacing = 2;
// when it hits this statement, the value will still be 1!!!
NSLog(#"%d", self.customObject.boxSpacing);
}
Do you see the problem? While I declared an instance variable with the underscore, _customObject, when the compiler hit the #synthesize statement, it created another instance variable, this time without the leading underscore, customObject. Thus, my explicitly declared instance variable never received the init/alloc and therefore is nil and thus any attempts to use it won't work!
Typically we see the converse problem (an explicitly declared instance variable without the underscore and a #synthesize statement of the form #synthesize customObject = _customObject), but hopefully you'll get the idea.
Anyway, this is the most common example of what would cause the behavior you describe. If this isn't what's going on, please provide us a more extensive code sample.
But if you're having problems, I'd always suggest that you check the value of your CustomClass object, itself, before you try to access its properties. Make sure the class object itself has been properly initialized (whether for the reasons I list above, or some other initialization problem) before you try to use its properties. You can do something like NSLog(#"CustomClass object = %#", customObject); or NSAssert(customObject, #"Object not properly initialized");.
Have you defined a #property for the CustomClass? If so, have you assigned a value?
If not, then you're sending the message variable to nil. And in your case that will result to 0.

Resources