Store references to Class in Dictionary and then instantiate class later? - objective-c

I would like to store a Class object in an NSMutableDictionary and then instantiate a copy later based on the key (specifically an NSNotification name). But unfortunately I can't find out what type of object 'Class' is in Objective-C, and its hard to google for. It's definitely not of a type 'id' so it won't go in the dictionary normally:
warning: Semantic Issue: Incompatible pointer types sending 'Class *' to parameter of type 'id'
Alternately I can store the Class' name in the dictionary, and use NSClassFromString to instantiate the class. It seems silly to use NSStringFromClass to put the Class in the Dictionary, and then NSClassFromString to get the class again. Is this my best option?

You can store a Class into a dictionary, e.g.
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
Class k = [NSString class];
[dict setObject:k forKey:#"foo"];
NSLog(#"%#", dict);
In fact, a Class is compatible with id. Note that you don't need a * after Class. It's like id.

Related

Transform NSCFDictionary to NSDictionary [duplicate]

I'm getting an NSCFDictionary returned to me and I can't figure out how to use it. I know it's of type NSCFDictionary because I printed the class and it came out as __NCSFDictionary. I can't figure out how to do anything with it.
I'm just trying to hold onto it for now but can't even get that to work:
NSDictionary *dict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];
for(NSURLProtectionSpace key in [dict keyEnumerator])
{
NSCFDictionary *value = [dict objectForKey:key];
}
The class reference for allCredentials says its supposed to return a dictionary whose values are also dictionaries. My assignment statement isn't working though. Do I need a cast of some kind?
NSDictionary and the other collection classes are actually class clusters: several concrete subclasses classes masquerading under the interface of a single class: they all provide the same functionality (because they are subclasses of the same class — in NSDictionary's case, this involves the three "primitive methods" -count, -objectForKey:, and -keyEnumerator), but have different internal workings to be efficient in different situations, based on how they're created and what type of data they may be storing.
NSCFDictionary is simply a concrete subclass of NSDictionary. That is, your NSDictionaries may actually be NSCFDictionary instances, but you should treat them as instances of NSDictionary, because that will provide you with the required dictionary-storage functionality.
NSDictionary *value = [dict objectForKey:key];
Now, another reason your code doesn't work: NSURLProtectionSpace is a class, so you should use it as a pointer, like this:
for (NSURLProtectionSpace *key ...
NSCFDictionary is the private subclass of NSDictionary that implements the actual functionality. It's just an NSDictionary. Just about any NSDictionary you use will be an NSCFDictionary under the hood. It doesn't matter to you code. You can type the variable as NSDictionary and use it accordingly.
I have an NSCFDictionary that is actually a NSMutableDictionary object, I can delete items from it. I mention this to further clarify jtbandes' answer: the NSCFDictionary object may be any object that inherits from NSDictionary.

Objective-C runtime issue setting object for NSMutableDictionary

I am coding a fairly complex system that uses a lot of metadata to manage dynamic objects. I am using various objective-c runtime features. I want to add stuff to mutable dictionaries which are properties within various classes. I want to do this where I know the class type and I know the property name, but I don't want to 'hard code' the assignment. If I hard-coded it I could do this:
[[(myknownclass*)localClassObjectInstance knownDictionary] setObject:value forKey:key];
but what I want to do is something like this:
[[unknownClassObjectInstance {aStringContainingTheDictionaryName}] setObject:value forKey:key];
How can I reference the mutable dictionary property when I only have the name of the property at runtime?
You can use -valueForKey: to run the method:
[(NSMutableDictionary*)[inst valueForKey:#"whateverMethodName"] setObject:o forKey:k];
Or alternatively, you can use performSelector:
SEL dictGetter = NSSelectorFromString(#"whateverMethodName");
NSMutableDictionary* dict = [inst performSelector:dictGetter];
[dict setObject:o forKey:k];

iOS KVC DRY mutation

I'm trying to map a dictionary of strings from a JSON fetch to a KVC compliant NSManagedObject, I can successfully use setValue: forKey: but i fail to see how I can map types.
For example I shouldn't be able to set a date to any random string: Printing description of myDate:
asdfsadf
however it worked.
I had a look at https://stackoverflow.com/a/5345023/828859 which provided some useful answers. I can go in and create validation for every single property... but that doesn't seem very DRY because ill have to validate every date and set the out value separately each time i have a date.
I would prefer to mutate by type before I use setValue: forKey: but I don't know how to discriminate on the property type.
What I want to do:
switch([object typeforkey:key]){
case #"NSDate":
//...
value = mutatedDate
//...
}
[object setValue:value forKey:key];
You can ask an object what kind of class it has been instantiated as. So you can do something like:
id myObject = [myDictionary objectForKey:key];
if ([myObject isKindOfClass:[NSDate class]]) {
// Do stuff
}
else if ([myObject isKindOfClass:[NSString class]]) {
// Do other stuff
}
This is because objects are structs containing a pointer with the ivar name isa pointing to an object of type Class, so you can always ask an object what kind of class it comes from.
I ended up using another dictionary for property type mapping. Then a object mapping object checks the object to be map abides by this particular protocol and uses the property type dictionary to convert each property before using setValue:forKey:.

What is an NSCFDictionary?

I'm getting an NSCFDictionary returned to me and I can't figure out how to use it. I know it's of type NSCFDictionary because I printed the class and it came out as __NCSFDictionary. I can't figure out how to do anything with it.
I'm just trying to hold onto it for now but can't even get that to work:
NSDictionary *dict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];
for(NSURLProtectionSpace key in [dict keyEnumerator])
{
NSCFDictionary *value = [dict objectForKey:key];
}
The class reference for allCredentials says its supposed to return a dictionary whose values are also dictionaries. My assignment statement isn't working though. Do I need a cast of some kind?
NSDictionary and the other collection classes are actually class clusters: several concrete subclasses classes masquerading under the interface of a single class: they all provide the same functionality (because they are subclasses of the same class — in NSDictionary's case, this involves the three "primitive methods" -count, -objectForKey:, and -keyEnumerator), but have different internal workings to be efficient in different situations, based on how they're created and what type of data they may be storing.
NSCFDictionary is simply a concrete subclass of NSDictionary. That is, your NSDictionaries may actually be NSCFDictionary instances, but you should treat them as instances of NSDictionary, because that will provide you with the required dictionary-storage functionality.
NSDictionary *value = [dict objectForKey:key];
Now, another reason your code doesn't work: NSURLProtectionSpace is a class, so you should use it as a pointer, like this:
for (NSURLProtectionSpace *key ...
NSCFDictionary is the private subclass of NSDictionary that implements the actual functionality. It's just an NSDictionary. Just about any NSDictionary you use will be an NSCFDictionary under the hood. It doesn't matter to you code. You can type the variable as NSDictionary and use it accordingly.
I have an NSCFDictionary that is actually a NSMutableDictionary object, I can delete items from it. I mention this to further clarify jtbandes' answer: the NSCFDictionary object may be any object that inherits from NSDictionary.

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