Cocoa bindings and KVC with CGColorRef - core-graphics

I'm trying to bind a CGColorRef on one of my objects to the "shadowColor" property of a CALayer. Unfortunately, I haven't been able to figure this out - it's probably something really simple!
The CGColorRef is implemented as a property:
#property (readwrite) CGColorRef labelShadowColor;
My binding is straight forward too:
[aLayer bind:#"shadowColor" toObject:aScreen withKeyPath:#"labelShadowColor" options:nil];
Where I'm coming unstuck is valueForUndefinedKey: - how would I implement this for a CGColorRef? I'm currently getting the boilerplate:
2009-08-09 03:13:50.056 Hyperspaces[33161:a0f] An uncaught exception was raised
2009-08-09 03:13:50.060 Hyperspaces[33161:a0f] [<HSScreen 0x100533930> valueForUndefinedKey:]: this class is not key value coding-compliant for the key labelShadowColor.
2009-08-09 03:13:50.064 Hyperspaces[33161:a0f] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<HSScreen 0x100533930> valueForUndefinedKey:]: this class is not key value coding-compliant for the key labelShadowColor.'
I can work around this by putting an NSColor property on both ends and setting the CALayer's "shadowColor" manually whenever the color is changed, but that seems inelegant.

OK, so here's a tip that I missed (and how I solved it):
You can't synthesize CGColorRefs (#synthesize someProperty;) - you need to declare the property #dynamic and implement the getters/setters, like so:
#dynamic labelShadowColor;
- (CGColorRef)labelShadowColor {
return labelShadowColor;
}
- (void)setLabelShadowColor:(CGColorRef)aShadowColor {
if (CGColorEqualToColor(labelShadowColor,aShadowColor)) return;
CGColorRelease(labelShadowColor);
if (aShadowColor != NULL) {
labelShadowColor = CGColorRetain(aShadowColor);
}
}
Then you'll also need to define valueForUndefinedKey: in your class:
- (id)valueForUndefinedKey:(NSString *)key {
if ([key isEqualToString:#"labelShadowColor"]) {
return (id)self.labelShadowColor;
}
return [super valueForUndefinedKey:key];
}
Once these two things were done, my bindings sprang into action!

Related

textFieldDidBeginEditing using tags

I'm running into a little issue with my textFieldDidBeginEditing method..
I'm trying to figure out which textfield is being called on to edit so I can decide if I want the view to move up or not to make the field visible.
Here is my method, I have commented some things out to try to find out where the error is:
- (void)textFieldDidBeginEditing:(UITextField *)sender
{
NSLog(#"This method is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
if(sender.tag == _nameF.tag)
{
NSLog(#"This if is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
}
else
{
NSLog(#"Else called instead");
}
}
I see "This method is called" in the log, so I know the method is being called in the first place, but after that, I see this:
2013-07-23 12:27:18.654 SidebarDemo[2110:60b] -[NSConcreteNotification tag]: unrecognized selector sent to instance 0x15d7b8c0
2013-07-23 12:27:18.655 SidebarDemo[2110:60b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteNotification tag]: unrecognized selector sent to instance 0x15d7b8c0'
This leads me to believe that it is something with sender.tag, but I don't see anything wrong with my code, to my knowledge.
What could the issue be here? Is there another method I can use to find out what textfield is being edited?
Thanks.
Since you are setting up the UITextFieldTextDidBeginEditingNotification notification to call your textFieldDidBeginEditing: method, you need to change the method parameter. And to avoid confusion with the corresponding UITextFieldDelegate method, you should rename this method as well (which means you need to update the line of code that register the notification handler).
- (void)textFieldDidBeginEditingHandler:(NSNotification *)notification {
UITextField *textField = (UITextField *)notification.object;
// It's OK to use == here since we really do want to compare pointer values
if(textField == _nameF) {
NSLog(#"This if is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
} else {
NSLog(#"Else called instead");
}
}
There is no need for tags since you have ivars for each text field.
BTW - why are you using notifications for this? Why not use the UITextFieldDelegate methods?

Objective-C & KeyValueCoding: How to avoid an exception with valueForKeyPath:?

I've got an object of type id and would like to know if it contains a value for a given keyPath:
[myObject valueForKeyPath:myKeyPath];
Now, I wrap it into a #try{ } #catch{} block to avoid exceptions when the given keypath isn't found. Is there a nicer way to do this? Check if the given keypath exists without handling exceptions?
Thanks a lot,
Stefan
You could try this:
if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
However, that may not correspond to the getter you have, especially if it is a boolean value. If this doesn't work for you, let me know and I'll write you up something using reflection.
For NSManagedObjects, an easy solution is to look at the object's entity description and see if there's an attribute with that key name. If there is, you can also take it to the next step and see what type of an attribute the value is.
Here's a simple method that given any NSManagedObject and any NSString as a key, will always return an NSString:
- (NSString *)valueOfItem:(NSManagedObject *)item asStringForKey:(NSString *)key {
NSEntityDescription *entity = [item entity];
NSDictionary *attributesByName = [entity attributesByName];
NSAttributeDescription *attribute = attributesByName[key];
if (!attribute) {
return #"---No Such Attribute Key---";
}
else if ([attribute attributeType] == NSUndefinedAttributeType) {
return #"---Undefined Attribute Type---";
}
else if ([attribute attributeType] == NSStringAttributeType) {
// return NSStrings as they are
return [item valueForKey:key];
}
else if ([attribute attributeType] < NSDateAttributeType) {
// this will be all of the NSNumber types
// return them as strings
return [[item valueForKey:key] stringValue];
}
// add more "else if" cases as desired for other types
else {
return #"---Unacceptable Attribute Type---";
}
}
If the key is invalid or the value can't be made into a string, the method returns an NSString error message (change those blocks to do whatever you want for those cases).
All of the NSNumber attribute types are returned as their stringValue representations. To handle other attribute types (e.g.: dates), simply add additional "else if" blocks. (see NSAttributeDescription Class Reference for more information).
If the object is a custom class of yours, you could override valueForUndefinedKey: on your object, to define what is returned when a keypath doesn't exist.
It should be possible to graft this behavior onto arbitrary classes reasonably simply. I present with confidence, but without warranty, the following code which you should be able to use to add a non-exception-throwing implementation of valueForUndefinedKey: to any class, with one, centralized line of code per class at app startup time. If you wanted to save even more code, you could make all the classes you wanted to have this behavior inherit from a common subclass of NSManagedObject and then apply this to that common class and all your subclasses would inherit the behavior. More details after, but here's the code:
Header (NSObject+ValueForUndefinedKeyAdding.h):
#interface NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;
#end
Implementation (NSObject+ValueForUndefinedKeyAdding.m):
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import <objc/runtime.h>
#import <objc/message.h>
#implementation NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler
{
Class clazz = self;
if (clazz == nil)
return;
if (clazz == [NSObject class] || clazz == [NSManagedObject class])
{
NSLog(#"Don't try to do this to %#; Really.", NSStringFromClass(clazz));
return;
}
SEL vfuk = #selector(valueForUndefinedKey:);
#synchronized([NSObject class])
{
Method nsoMethod = class_getInstanceMethod([NSObject class], vfuk);
Method nsmoMethod = class_getInstanceMethod([NSManagedObject class], vfuk);
Method origMethod = class_getInstanceMethod(clazz, vfuk);
if (origMethod != nsoMethod && origMethod != nsmoMethod)
{
NSLog(#"%# already has a custom %# implementation. Replacing that would likely break stuff.",
NSStringFromClass(clazz), NSStringFromSelector(vfuk));
return;
}
if(!class_addMethod(clazz, vfuk, handler, method_getTypeEncoding(nsoMethod)))
{
NSLog(#"Could not add valueForUndefinedKey: method to class: %#", NSStringFromClass(clazz));
}
}
}
#end
Then, in your AppDelegate class (or really anywhere, but it probably makes sense to put it somewhere central, so you know where to find it when you want to add or remove classes from the list) put this code which adds this functionality to classes of your choosing at startup time:
#import "MyAppDelegate.h"
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import "MyOtherClass1.h"
#import "MyOtherClass2.h"
#import "MyOtherClass3.h"
static id ExceptionlessVFUKIMP(id self, SEL cmd, NSString* inKey)
{
NSLog(#"Not throwing an exception for undefined key: %# on instance of %#", inKey, [self class]);
return nil;
}
#implementation MyAppDelegate
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MyOtherClass1 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass2 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass3 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
});
}
// ... rest of app delegate class ...
#end
What I'm doing here is adding a custom implementation for valueForUndefinedKey: to the classes MyOtherClass1, 2 & 3. The example implementation I've provided just NSLogs and returns nil, but you can change the implementation to do whatever you want, by changing the code in ExceptionlessVFUKIMP. If you remove the NSLog, and just return nil, I suspect you'll get what you want, based on your question.
This code NEVER swizzles methods, it only adds one if it's not there. I've put in checks to prevent this from being used on classes that already have their own custom implementations of valueForUndefinedKey: because if someone put that method in their class, there's going to be an expectation that it will continue to get called. Also note that there may be AppKit code that EXPECTS the exceptions from the NSObject/NSManagedObject implementations to be thrown. (I don't know that for sure, but it's a possibility to consider.)
A few notes:
NSManagedObject provides a custom implementation for valueForUndefinedKey: Stepping through its assembly in the debugger, all it appears to do is throw roughly the same exception with a slightly different message. Based on that 5 minute debugger investigation, I feel like it ought to be safe to use this with NSManagedObject subclasses, but I'm not 100% sure -- there could be some behavior in there that I didn't catch. Beware.
Also, as it stands, if you use this approach, you don't have a good way to know if valueForKey: is returning nil because the keyPath is valid and the state happened to be nil, or if it's returning nil because the keyPath is invalid and the grafted-on handler returned nil. To do that, you'd need to do something different, and implementation specific. (Perhaps return [NSNull null] or some other sentinel value, or set some flag in thread-local storage that you could check, but at this point is it really all that much easier than #try/#catch?) Just something to be aware of.
This appears to work pretty well for me; Hope it's useful to you.
There's no easy way to solve this. Key Value Coding (KVC) isn't intended to be used that way.
One thing is for sure: using #try-#catch is really bad since you're very likely to leak memory etc. Exceptions in ObjC / iOS are not intended for normal program flow. They're also very expensive (both throwing and setting up the #try-#catch IIRC).
If you look at the Foundation/NSKeyValueCoding.h header, the comment / documentation for
- (id)valueForKey:(NSString *)key;
clearly states which methods need to be implemented for -valueForKey: to work. This may even use direct ivar access. You would have to check each one in the order described there. You need to take the key path, split it up based on . and check each part on each subsequent object. To access ivars, you need to use the ObjC runtime. Look at objc/runtime.h.
All of this is vary hacky, though. What you probably want is for your objects to implement some formal protocol and then check -conformsToProtocol: before calling.
Are your key paths random strings or are those strings under your control? What are you trying to achieve? Are you solving the wrong problem?
I don't believe this is possible in a safe way (i.e. without mucking with -valueForUndefinedKey: or something similar on other peoples' classes). I say that because on the Mac side of things, Cocoa Bindings—which can be set to substitute a default value for invalid key paths—simply catches the exceptions that result from bad key paths. If even Apple's engineers don't have a way to test if a key path is valid without trying it and catching the exception, I have to assume that such a way doesn't exist.

Unrecognized selector sent to instance - why?

OK so I have a code with an class object called "game". Every frame (60 FPS) I update that object with function that gets a string. After like 5 seconds of running the game I'm getting the unrecognized selector sent to instance error.
The update:
[game updatePlayersAndMonsters:#"0" monsters:#"0"];
The function:
-(void)updatePlayersAndMonsters:(NSString*)players monsters:(NSString*)monsters {
CCLOG(#"%#.%#", players, monsters);
}
I don't understand what's going on.
The error:
2011-07-03 12:13:19.175 app[65708:207] -[NSCFString updatePlayersAndMonsters:monsters:]: unrecognized selector sent to instance 0xc4e95b0
2011-07-03 12:13:19.176 app[65708:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString updatePlayersAndMonsters:monsters:]: unrecognized selector sent to instance 0xc4e95b0'
What should I do? Thanks. Also IDK if any other details you need, so just write if I forget something, I just don't have an idea.
UPDATE:
Gmae is object of class GameNode:
+(id) GmameNodeWithMapID:(int)MapID_ scene:(SomeScene*)MainScene_ players:(NSString*)Cplayers_ monsters:(NSString*)Cmonsters_ monsterCount:(NSString*)monsterCount_
{
return [[[self alloc] GmameNodeWithMapID:MapID_ scene:MainScene_ players:Cplayers_ monsters:Cmonsters_ monsterCount:monsterCount_] autorelease];
}
-(id) GmameNodeWithMapID:(int)MapID scene:(SomeScene*)MainScene players:(NSString*)Cplayers monsters:(NSString*)Cmonsters monsterCount:(NSString*)monsterCount
{
if( (self=[super init])) {
I create it with:
game = [GameNode GmameNodeWithMapID:ChoosenMapID scene:self players:Thing[5] monsters:Thing[6] monsterCount:Thing[4]];
UPDATE 2
I create the SomeScene:
+(id) scene {
CCScene *s = [CCScene node];
id node = [SomeScene node];
[s addChild:node];
return s;
}
-(id) init {
if( (self=[super init])) {
I use it:
[[CCDirector sharedDirector] replaceScene: [CCTransitionRadialCW transitionWithDuration:1.0f scene:[LoginScene scene]]];
Since you imply that the update function [game updatePlayersAndMonsters:#"0" monsters:#"0"]; is called for the first 5 seconds of your game and then you get the error, my guess is that the game object is not correctly retained, so it gets deallocated and the successive attempt of sending a message to it fails because some NSString object has been reusing its memory (and it does not have a updatePlayersAndMonsters:monsters selector).
Please share how game is created (alloc/init) and how it is stored in your classes to help you further.
Activating NSZombies tracking could also help to diagnose this.
EDIT: after you adding the code
It seems to me that in the line:
game = [GameNode GmameNodeWithMapID:ChoosenMapID scene:self players:Thing[5] monsters:Thing[6] monsterCount:Thing[4]];
you are setting either a local variable or an ivar to your autoreleased GameNode.
Now, since you are not using a property, nor I can see any retain on your autoreleased GameNode, my hypothesis seems confirmed. Either assign to a retain property:
self.game = [GameNode ...];
being game declared as:
#property (nonatomic, retain)...
or do a retain yourself:
game = [[GameNode GmameNodeWithMapID:ChoosenMapID scene:self players:Thing[5] monsters:Thing[6] monsterCount:Thing[4]] retain];
'NSInvalidArgumentException', reason: '-[NSCFString updatePlayersAndMonsters:monsters:]: means tht you are trying to send updatePlayersAndMonsters to a String object. Are you reassigning game to point to something else?

Objective C/NSMutableDictionary - [NSCFSet objectForKey:]: unrecognized selector

I do not understand what I am doing wrong. I have a dictionary as a property of a singleton class:
#interface CABResourceManager : NSObject
{
....
NSMutableDictionary* soundMap;
}
#property (retain) NSMutableDictionary *soundMap;
Then I add an object to this dictionary in one class method:
+ (void)loadSoundFromInfo:(ABSoundInfo)sound
{
static unsigned int currentSoundID = 0;
CABSound* newSound = [[CABSound alloc] initWithInfo:(ABSoundInfo)sound soundID:++currentSoundID];
[[CABResourceManager sharedResMgr].soundMap setObject:newSound forKey:sound.name];
}
And try to get it in another method:
+ (ALuint)playSoundByName:(NSString*)name
{
NSMutableDictionary* map = [CABResourceManager sharedResMgr].soundMap;
CABSound *sound = [map objectForKey:name]; // here comes the exception
and the app exits on exception by that.
2011-03-27 20:46:53.943 Book3HD-EN[5485:207] *** -[NSCFSet objectForKey:]: unrecognized selector sent to instance 0x226950
2011-03-27 20:46:53.945 Book3HD-EN[5485:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException'
I guess it might have something with memory management, but hier it looks clear for me: CABSound object is retained in dictionary by doing setObject(), it should not be released at this time.
I'd check that soundMap is properly initialized. It looks like soundMap is a bad pointer at the time you get the error. It might happen to be nil in +loadSoundFromInfo, which wouldn't produce an error right away.
Make sure that you've initialized your soundMap in designated initializer:
// - (id) init... or something else
soundMap = [[NSMutableDictionary alloc] init];
Dont forget to override default dealloc implementation:
// class implementation file
- (void)dealloc {
[soundMap release];
//...release other objects you own...
[super dealloc];
}

how to initialize an object of subclass with an "existing object of superclass" in objective-C

I have subclassed NSException class to create CustomException class.
Whenever I catch an exception in code (in #catch), i want to initialize an object of CustomException (subclass of NSException) with the object of NSException that is passed as a parameter to #catch.
Something like this
#catch (NSException * e) {
CustomException * ex1=[[CustomException alloc]initWithException:e errorCode:#"-11011" severity:1];
}
I tried doing it by passing the NSException object to the init method of CustomException.
(i replaced the [super init] with the passed NSException object as given below)
//initializer for CustomException
-(id) initWithException:(id)originalException errorCode: (NSString *)errorCode severity:(NSInteger)errorSeverity{
//self = [super initWithName: name reason:#"" userInfo:nil];
self=originalException;
self.code=errorCode;
self.severity=errorSeverity;
return self;
}
This doen't work! How can I achieve this?
Thanks in advance
You are trying to do something which generally[*] isn't supported in any OO language - convert an instance of a class into an instance of one of its subclasses.
Your assignment self=originalException is just (type incorrect) pointer assignment (which the compiler and runtime will not check as you've used id as the type) - this won't make CustomException out of an NSException.
To achieve what you want replace self=originalException with
[super initWithName:[originalException name]
reason:[originalException reason]
userInfo:[originalException userInfo]]
and then continue to initialize the fields you've added in CustomException.
[*] In Objective-C it is possible in some cases to correctly convert a class instance into a subclass instance, but don't do it without very very very very good reason. And if you don't know how you shouldn't even be thinking of it ;-)
self = originalException;
When you do that you are just assigning a NSException object to your NSCustomException so
the following things will happen:
You might get a warning when doing
the assignment since
CustomException is expected but you
are passing just a NSException
object ;(
After that, the compiler will think that self
is a CustomException object so it
won't complain when calling some
methods of CustomException class
but it will crash when reaching
those.
You should enable initWithName:reason:userinfo: and don't do the assignment.