Add a NSXPCConnection as a key to a NSMutableDictionary - objective-c

Edit:
I solved this problem by adding the process identifier that is exposed from the NSXPCConnection class as that is unique per connection as the key.
Problem:
I'm facing a problem where I need to store a key-value pair in a NSMutableDictionary (where an object of type NSXPCConnection is stored as the key). But I'm unable to do so as NSXPCConnection does not conform to NSCopying.
This is my code:
- (void)addDataToDict:(MyClass *)obj Connection:(NSXPCConnection *)connection
{
assert(obj);
LogInfo("Assert success");
if([_dict objectForKey:connection])
{
LogError("Connection already in dictionary, abort");
return;
}
else
LogInfo("Adding connection to dict");
NSObject<NSCopying> *key = connection;
[_dict setObject:obj forKey:key];
}
But this throws the following warning:
Incompatible pointer types initializing 'NSObject<NSCopying> *' with an expression of type 'NSXPCConnection *'
and the following error during runtime:
-[NSXPCConnection copyWithZone:]: unrecognized selector sent to instance 0x127a4b0d0
Any suggestions on how I can do this better are welcome!

Although you've already solved this for your specific use-case by using a different key type, one alternative is to use an NSMapTable, which behaves very much like NSDictionary but allows you to have different semantics for keys and values.
Specifically, for this use-case, a +strongToStrongObjectsMapTable would be appropriate as it retains keys instead of copying them, allowing you to key by non-NSCopying objects like NSXPCConnection.

Related

Why does valueForKey throw an exception when used on dispatch_queue_t instance variable?

This is for unit testing purposes only
I'm trying to access a dispatch_queue_t that is a protected instance variable of an objective C object for unit testing purposes. I'm using valueForKey to access the protected instance variables that are objects, is there an equivalent call I can make for dispatch_queue_t?
Thank you
Edit: I've read that access to these values is possible, I just can't find a explanation of how.
Edit2: When I try calling valueForKey I get
2013-09-24 14:17:35.235 MyApp[7729:a0b] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key _queue.'
Edit3: I just tested and realized it should work with structs, the one that is throwing the exception is the retrieval of a dispatch_queue_t
Edit4: This was in code that is using Automatic Reference Counting
Use the same -valueForKey: as for objects.
Calling -valueForKey: will always return you an object, but in case of accessing non-object types, the value is wrapped in NSValue object. In case of structs it's NSValue directly, in case of numbers (int, float) it uses NSNumber subclass.
After you got NSValue, this is how you unwrap the struct:
NSValue *value = [object valueForKey:#"key"];
struct Something s;
[value getValue:&s];
// `s` contains the desired struct
Solution found:
The alternative way to access the instance variable is with the following code using object_getIvar:
Ivar queueIvar = class_getInstanceVariable([_myObject class], "_queue");
_privateQueue = (__bridge dispatch_queue_t)(object_getIvar(_myObject, queueIvar));

-[NSNull objectForKeyedSubscript:]: unrecognized selector sent to instance

I got an exception that says:
-[NSNull objectForKeyedSubscript:]: unrecognized selector sent to instance
Is it saying I am trying to access an NSNull object with a key?
Any idea what causes this and how to fix it or debug further?
The way to fix it is to not attempt objectForKeyedSubscript on an NSNull object. (I'm betting you're handling some JSON data and aren't prepared for a NULL value.)
(And apparently objectForKeyedSubscript is what the new array[x] notation translates into.)
(Note that you can test for NSNull by simply comparing with == to [NSNull null], since there's one and only one NSNull object in the app.)
What ever value you are storing, despite what the editor tells you, at run time you are storing an NSNull, and later on trying to call objectForKeyedSubscript. I am guessing this happening on what is expected to be an NSDictionary. Some thing like:
NSString *str = dict[#"SomeKey"]
Either a piece of code beforehand is not doing its job and investigate there, or perform some validation:
NSDictionary *dict = ...;
if ( [dict isKindOfClass:[NSDictionary class]] ) {
// handle the dictionary
}
else {
// some kind of error, handle appropriately
}
I often have this kind of scenario when dealing with error messages from networking operations.
I suggest adding a category to NSNull to handle this in the same way you would expect a subscript call to be handled if it it were sent to nil.
#implementation NSNull (Additions)
- (NSObject*)objectForKeyedSubscript:(id<NSCopying>)key {
return nil;
}
- (NSObject*)objectAtIndexedSubscript:(NSUInteger)idx {
return nil;
}
#end
A simple way to test is like this:
id n = [NSNull null];
n[#""];
n[0];
With this category, this test should be handled successfully/softly.

how to handle an data from server without knowing the object type (aka, could be a dictionary or array)

so i make a lot of server calls for my app. what is returned can depend on the result of the server operation.
say i make an api call to "foo" which will return either a hash map/nsdictionary if successful or a bool (or a 0 for false, meaning it did not execute).
with my code, i typecast it to i believe it should be assuming it was a successful operation. i will check to see if i get back something else then i expected, say a BOOL false.
NSString *mapContext = (NSString *) [call xmlrpcCall:#"load_map" withObjects: [NSArray arrayWithObjects:dataCenter.state,nil]];
NSLog(#"mapContext in loadStateMap: %#", mapContext);
if ([mapContext isKindOfClass:[NSDictionary class]])
{
if ([mapContext objectForKey:#"faultCode"])
{
NSLog(#"mapContext: %#", mapContext);
[self defaultAlert:mapContext titleMsg:#"load_map"];
}
}
here i ask the server to load a map. if successfull, it will return a string. if it fails, it will return a dictionary with a fault code and a fault message. since mapContext is instantiated as a string, when i check to see if its a dictionary and check for a key fault code, xcode gives me a warning that mapContext may not respond to "objectForKey". i understand completely why i get the warning, but is there a way i can prevent the warning? it never breaks the app but its annoying to see 30+ warnings about this issue.
Use id, this is what it is for and that is why so many abstracted foundation classes use them (NSArray anyone).
//problem solved!
id mapContext = [call xmlrpcCall:#"load_map" withObjects: [NSArray arrayWithObjects:dataCenter.state,nil]];

Cocoa: deserialize json string to custom objects (not NSDictionary, NSArray)

In java-land, there are a handful of useful libraries which will convert json strings to objects of matching type. The json libraries I've seen for cocoa simply create nested NSDictionaries and NSArrays. Is there a tool out there which will go the extra step of reconstituting whatever object type I want?
So, for example, if I have a class called "Unicorn", with a property "maneColor", and I have json that looks like this:
{
"maneColor":"silver"
}
I can automatically instantiate a Unicorn object with "maneColor" set to "silver".
I'm not aware of any specific implementations, but key-value coding gets you very close to what you want: Key Value Coding Guide. I've had good results combining streamed json parsing with KVC.
The -setValue:forKey: method makes adapting serialized data to custom objects fairly straightforward. To continue with your example, you'd create a Unicorn class with all required accessor methods: -setName:/-name, -setManeColor/-maneColor, etc. (You may be able to use properties for some expected values, but there are cases, as with the maneColor value, where you probably want to write a custom setter to convert from the color name string to an NSColor or UIColor object.)
You'll also want to add two more methods to your custom object: -setValue:forUndefinedKey: and -valueForUndefinedKey:. These are the methods that will be called if your object has no accessor methods matching a key passed into the KVC methods. You can catch unexpected or unsupported values here, and store them or ignore them as necessary.
When you send -setValue:forKey: to the Unicorn object, the framework looks for accessors matching the key pattern. For instance, if the key is "maneColor" and you're setting the value, the framework checks to see if your object implements -setManeColor:. If so, it invokes that method, passing in the value; otherwise, -setValue:forUndefinedKey: is called, and if your object doesn't implement it, an exception is thrown.
When your parser's delegate receives notification that parsing a json unicorn object has begun, instantiate a Unicorn object. As your parser returns the parsed data to you, use -setValue:forKey: to add the data to your object:
- ( void )parserDidBeginParsingDictionary: (SomeParser *)p
{
self.currentUnicorn = [ Unicorn unicorn ];
}
- ( void )parser: (SomeParser *)p didParseString: (NSString *)string
forKey: (NSString *)key
{
[ self.currentUnicorn setValue: string forKey: key ]
}
- ( void )parserDidFinishParsingDictionary: (SomeParser *)p
{
[ self.unicorns addObject: self.currentUnicorn ];
}
Use Jastor - https://github.com/elado/jastor
Takes already parsed JSON into NSDictionary and fills an instance of real Objective-C class.
NSDictionary *parsedJSON = (yajl, JSONKit etc)
Unicorn *unicorn = [[Unicorn alloc] initWithDictionary:parsedJSON];
unicorn.maneColor // "silver"
As any subclass of NSObject conforms to NSKeyValueCoding protocol:
NSDictionary *parsedJSON = //whatever
id <NSKeyValueCoding> entity = [[CustomNSObjectSubclass alloc] init];
[entity setValuesForKeysWithDictionary:parsedJSON];
Apple added the NSJSONSerialization class to iOS 5.0 which, according to the documentation, does the following:
You use the NSJSONSerialization class to convert JSON to Foundation
objects and convert Foundation objects to JSON.
An object that may be converted to JSON must have the following
properties:
The top level object is an NSArray or NSDictionary. All objects are
instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull. All
dictionary keys are instances of NSString. Numbers are not NaN or
infinity.
Here's a tutorial and wrapper method to get you started.

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,
};