Unable to use 'class' as a key in NSDictionary - objective-c

I'm trying to use a class as a key in an NSDictionary. I looked at the answer to this question and what I have is pretty much the same; I'm using setObject: forKey:. However, XCode complains, saying Incompatible pointer types sending 'Class' to parameter of type 'id<NSCopying>'. The call I have is:
[_bugTypeToSerializerDictionary setObject: bugToStringSerializer
forKey: [bugToStringSerializer serializedObjectType]];
bugToStringSerializer is an instance of BugToStringSerializer whose concrete implementations implement serializedObjectType. An example of a concrete implementation looks like this:
- (Class) serializedObjectType {
return [InfectableBug class];
}
What am I doing wrong here?

(It seems that classes do conform to NSCopying, however their type is not id <NSCopying>.) Edit: classes do not conform to protocols. Of course the essential is that classes respond to the copy and copyWithZone: messages (and that's why you can safely ignore the warning in this case). Their type is still not id <NSCopying>.) That's why the compiler complains.
If you really don't want that ugly warning, just perform an explicit type conversion:
[dictionary setObject:object forKey:(id <NSCopying>)someClass];

Aha,I just fixed the bug in my project.
use this:
NSStringFromClass([Someclass class]);

The other answers are certainly helpful, but in this case it probably makes more sense to just use an NSMapTable, which does not copy the key unlike NSDictionary, and just retains it with a strong pointer (by default, although this can be changed).
Then you can just use your original code with no modifications.
NSMapTable *_bugTypeToSerializerDictionary = [NSMapTable new];
...
[_bugTypeToSerializerDictionary setObject: bugToStringSerializer
forKey: [bugToStringSerializer serializedObjectType]];
It's less hacky, and is clearer at conveying programmer intent.
For extra style points you could give the instance variable a slightly more fitting name like_bugTypeToSerializerMap.

This is my usual code:
#{
(id)[MyClass1 class] : #1,
(id)[MyClass2 class] : #2,
(id)[MyClass3 class] : #3,
(id)[MyClass4 class] : #4,
};
But recently I've discovered this approach:
#{
MyClass1.self : #1,
MyClass2.self : #2,
MyClass3.self : #3,
MyClass4.self : #4,
};

Related

XPC not registering classes correctly for collection

I'm using XPC in one of my apps on 10.8. It's got the standard setup with protocols defined for exported interface and the remote interface. The problem I run into is with one of my methods on the exported interface.
I have a model class, lets just call it Foo. This class conforms to NSSecureCoding, implements +supportsSecureCoding, and encodes/decodes the internal properties correctly using the secure coding methods. When passing this object through a method on my exported interface that only involves a single instance, it works fine.
The problem occurs when I want to pass a collection of these objects, or a NSArray of Foo objects. Here's an example of what the signature on the exported interface looks like:
- (void)grabSomethingWithCompletion:(void (^)(NSArray *foos))completion;
And I've whitelisted the Foo class, as noted in the documentation:
NSSet *classes = [NSSet setWithObject:Foo.class];
[exportedInterface setClasses:classes forSelector:#selector(grabSomethingWithCompletion:) argumentIndex:0 ofReply:YES];
Now this should make it so that this array can be safely copied across the process and decoded on the other side. Unfortunately this doesn't seem to be working as expected.
When calling the method on the exported protocol, I receive an exception:
Warning: Exception caught during decoding of received reply to message
'grabSomethingWithCompletion:', dropping incoming message and
calling failure block.
Exception: Exception while decoding argument 1 of invocation:
return value: {v} void target: {#?} 0x0
(block) argument 1: {#} 0x0
Exception: value for key 'NS.objects' was of unexpected class
'Foo'. Allowed classes are '{(
NSNumber,
NSArray,
NSDictionary,
NSString,
NSDate,
NSData )}'.
This almost seems like it didn't even register the whitelisting I performed earlier. Any thoughts?
EDIT 2: It depends on where you've whitelisted Foo. It needs to be whitelisted from within whatever is calling grabSomethingWithCompletion:. For instance, if you have a service that implements and exposes:
- (void)takeThese:(NSArray *)bars reply:(void (^)(NSArray *foos))completion;
Then you need the service side to whitelist Bar for the incoming connection:
// Bar and whatever Bar contains.
NSSet *incomingClasses = [NSSet setWithObjects:[Bar class], [NSString class], nil];
NSXPCInterface *exposedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(InYourFaceInterface)];
[exposedInterface setClasses:incomingClasses forSelector:#selector(takeThese:reply:) argumentIndex:0 ofReply:NO];
// The next line doesn't do anything.
[exposedInterface setClasses:incomingClasses forSelector:#selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.exposedInterface = exposedInterface;
That second section has to go on the other end of the connection, whatever is talking to your service:
NSSet *incomingClasses = [NSSet setWithObjects:[Foo class], [NSNumber class], nil];
NSXPCInterface *remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(InYourFaceInterface)];
[remoteObjectInterface setClasses:incomingClasses forSelector:#selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.remoteObjectInterface = remoteObjectInterface;
In summary, whatever is receiving strange objects needs to be the one whitelisting the strange objects. Not sure if this was your problem, but I'm sure it will be somebody's.
EDIT: Now that I've been working with XPC for a while longer, I realize that my answer, while solving a problem, does not solve your problem. I've run into this now a couple different times and I'm still not sure how to fix it outside of implementing my own collection class, which is less than ideal.
Original Answer:
I know it has been quite some time since you asked this, but after a ton of searching with no one answering this question, I thought I'd post my answer for what was causing it (there may be other causes, but this fixed it for me).
In the class that conforms to NSSecureCoding, in the initWithCoder: method, you need to explicitly decode collections by passing in a set of all possible classes contained within the collection. The first two are standard examples of decoding, and the last one is decoding a collection:
if (self = [super init]) {
self.bar = [aDecoder decodeInt64ForKey:#"bar"];
self.baz = [aDecoder decodeObjectOfClass:[Baz class] forKey:#"baz"];
NSSet *possibleClasses = [NSSet setWithObjects:[Collection class], [Foo class], nil];
self.foo = [aDecoder decodeObjectOfClasses:possibleClasses forKey:#"foo"];
}
So if you collection is a set containing NSStrings, possible classes would be [NSSet class] and [NSString class].
I'm sure you've moved on from this problem, but maybe someone else needs this answer as much as I did.
I encountered this same problem, I had to explicitly whitelist NSArray* as well
NSSet *classes = [NSSet setWithObjects: [Foo class], [NSArray class], nil];
Which is a bit counterintuitive since the documentation does not mention this requirement.
Actually it seems you need to add your custom class to the already whitelisted ones :
NSSet currentClasses = [remoteObjectInterface classesForSelector:#selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
NSSet *allIncomingClasses = [currentClasses setByAddingObjectsFromSet:[NSSet setWithObjects:[Foo class], [NSNumber class], nil];
NSXPCInterface *remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(InYourFaceInterface)];
[remoteObjectInterface setClasses:allIncomingClasses forSelector:#selector(takeThese:reply:) argumentIndex:0 ofReply:YES];
xpcConnection.remoteObjectInterface = remoteObjectInterface;

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.

incompatible pointer type

I have this class:
#interface G2Matrix : NSObject
...
- (id) initWithArray:(float *)val;
...
#end
This line below give me a warning saying that the first argument to the method initWithArray has an incompatible pointer type:
float m[16];
...
G2Matrix* matrix = [[[G2Matrix alloc] initWithArray:m] autorelease];
If I change the method name to something like initWithArray1 the warning disappears. I know that some objects in foundation classes have a method with the same name, but I am deriving from NSObject, which doesn't have this method. What gives?
Additional info - I call the same initWithArray method from other init methods in the G2Matrix class, but I don't see the warning there.
At a guess, this is a type problem:
Inside the other init methods, you call [self initWithArray:...]. self is typed as a G2Matrix*. In this context the compiler can fully resolve which imp (C function pointer) will eventually handle the method call, and detect its signature (argument and return types) correctly.
Out in regular code, [G2Matrix alloc] returns an id. In this context the compiler can only tell the method selector, which will be bound to an imp at runtime. It has to guess which initWithArray: you mean, and as you can see from the warning it guesses wrong, since a foundation class has an initWithArray: method with a different signature. Your code does still work, the compiler just can't be certain.
Picking a unique name for the initMethod (initWithFloats: maybe?) is the recommended way to shut the warning up. Other ways are: break it into two lines; or cast the alloc return value to the right class:
G2Matrix *matrix = [G2Matrix alloc];
matrix = [[matrix initWithArray:pointerToFloats] autorelease];
// or
G2Matrix* matrix = [[(G2Matrix *)[G2Matrix alloc] initWithArray:m] autorelease];
Looks a little odd, but allows you to turn the treat-warnings-as-errors compiler flag back on.
#tathagata thats because initWithArray is method defined in NSArray class so you cannot use it unless you subclass NSArray class.
see the documentation on NSArray
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
PS.
by use the method, i meant Override the existing method for your purpose which is not a good idea you can find the Subclassing Notes in the document.

Defining your own key path operators in cocoa

Is it possible to define your own key path operators, such as #avg, #sum, etc…
Short answer: Kinda. You can override valueForKeyPath: to intercept your custom operator or forward on to super, but that can be problematic (I'll leave the explanation to that as an exercise to the reader).
Long answer: Yes you can, but it relies on using private behavior (not private api).
After some neat introspection of NSArray, I found some private methods:
_distinctUnionOfSetsForKeyPath:
_distinctUnionOfObjectsForKeyPath:
_distinctUnionOfArraysForKeyPath:
_unionOfSetsForKeyPath:
_unionOfArraysForKeyPath:
_unionOfObjectsForKeyPath:
_minForKeyPath:
_maxForKeyPath:
_countForKeyPath:
_avgForKeyPath:
_sumForKeyPath:
Well, neat! Those methods seem to match the operators you can use with collections: #sum, #min, #max, #distinctUnionOfObjects, etc. The # has been replaced with an underscore and we've got ForKeyPath: appended.
So it would seem that we can create a new method to match the appropriate signature and we're good to go.
So:
#interface NSArray (CustomOperator)
- (id) _fooForKeyPath:(NSString *)keyPath;
#end
#implementation NSArray (CustomOperator)
- (id) _fooForKeyPath:(NSString *)keyPath {
//keyPath will be what comes after the keyPath. In this example, it will be "self"
return #"Hello world!";
}
#end
NSArray * array = [NSArray arrayWithObjects:#"1", #"2", #"3", nil];
NSLog(#"%#", [array valueForKeyPath:#"#foo.SELF"]); //logs "Hello world!"
It works, but I'm not sure I would rely on this, since it relies on an implementation detail that could change in the future.
It's possible by overriding valueForKeyPath: and doing your own custom logic in there, but there's no framework support for it.

Using class as key in NSDictionary

I'm writing a contextual "factory" that will maintain a dictionary of converter/acting objects which inherit from some Converter class. This class has a method:
- (Class)classResponsibility
Or something similar, such that a StringConverter class would implement the method as:
- (Class)classResponsibility {
return [NSString class];
}
Then to store that converter in the dictionary, I had hoped on doing something like:
[converters setValue:stringConverter forKey:[stringConverter classResponsibility]];
But the compiler complains that the type "Class" is an invalid parameter type for argument 2 of the setValue:forKey: method. I had wanted to avoid setting the key as the Class's name ("NSString"), but if that's the best solution than I'll go with it.
You're using setValue:forKey: which only takes NSStrings as keys. you should be using setObject:forKey: instead. A class object (pointers to class objects can be passed as type Class) is a full-fledged Objective-C object (a class object is an instance of its meta-class, and you can use all the NSObject methods on a class object; read more about meta-classes here), so they can be used anywhere objects are used.
Another requirement for keys of a dictionary is that they support copying (i.e. have the copyWithZone: method. Do class objects support this method? In fact, it does. The NSObject class defines a class method +copyWithZone:, whose documentation explicitly says that it "lets you use a class object as a key to an NSDictionary object". I think that's the answer to your question.
Your other option is to use [NSValue valueWithNonretainedObject:yourObjectHere] to construct the key from something other than a string. I ran into a similar problem and I wanted to use a CoreData object as the key and something else as the value. This NSValue method worked perfect and I believe was it's original intent. To get back to the original value just call nonretainedObjectValue
While a Class object makes a perfectly good key in an NSDictionary, it's worth mentioning NSMapTable, which is modeled after NSDictionary, but provides more flexibility as to what kind of objects are suitable for use as keys and/or values, documented to support weak references and arbitrary pointers.
-setValue:forKey: is documented to take an NSString as the second parameter. You'll have to use NSStringFromClass() and NSClassFromString() as adaptors.
I was looking for the setObject:forKey: method instead of setValue:forKey:. The method signature for setObject:forKey: accepts (id) as both parameter types, and is much better suited.
I just had a similar situation crop up with the exact same error message:
[tempDictionary setObject:someDictionary forKey:someClass];
All I did was implement the NSCopying protocol in someClass:
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] allocWithZone:zone] init];
[copy setId:[self id]];
[copy setTitle:[self title]];
return copy;
}
I think what was happening was that a copy of someClass was being made in order to be used as the key, but since my object didn't know how to copy itself (deriving from NSObject it didn't have a copyWithZone in the superclass) it balked.
One thing I've found with my approach is that it's use an object as a key. Unless I already have the object instantiated, I'm constantly calling allKeys or just otherwise enumerating over the dictionary.
[After writing this, I see that you want to store the class as such as the key. I'm leaving this out there because I would have saved a lot of time if I had found my answer when I was searching SO. I didn't find anything like this then.]
You can use classes as NSDictionary's keys like this:
#{
(id)[MyClass1 class] : #1,
(id)[MyClass2 class] : #2,
(id)[MyClass3 class] : #3,
(id)[MyClass4 class] : #4,
};
Or even like this:
#{
MyClass1.self : #1,
MyClass2.self : #2,
MyClass3.self : #3,
MyClass4.self : #4,
};