I am trying to implement a subclass of NSMutableDictionary that returns nil instead of throwing a NSUndefinedKeyException when the key is not present in the Dictionary.
However when I try to add objects to my dictionary I get
[NSMutableDictionary setObject:forKey:]: method only defined for abstract class
NilDictionary.h
#interface NilDictionary : NSMutableDictionary {
}
#end
NilDctionary.m
#implementation NilDictionary
- (id)valueForUndefinedKey:(NSString *)key {
return nil;
}
#end
Do I really have to implement all the methods from NSMutableDictionary again in my subclass or is there some other class I should be subclassing?
Clarification: My original problem came down to me not being able to read the documentation properly.
If you need to subclass NSMutableDictionary check out the correct answer. If you want a dictionary that returns nil when your key is not present, NSMutableDictionary does that already.
NSMutableDictionary Class Reference says:
In a subclass, you must override both of its primitive methods:
1. setObject:forKey:
2. removeObjectForKey:
You must also override the primitive methods of the NSDictionary class.
NSDictionary Class Reference says:
If you do need to subclass NSDictionary, you need to take into account that is represented by a Class cluster—there are therefore several primitive methods upon which the methods are conceptually based:
1. count
2. objectForKey:
3. keyEnumerator
4. initWithObjects:forKeys:count:
In a subclass, you must override all these methods.
NSDictionary’s other methods operate by invoking one or more of these primitives. The non-primitive methods provide convenient ways of accessing multiple entries at once.
It seems that you need to override all these six methods to make your NSMutableDictionary subclass work perfect.
Here's your problem. NSDictionary (and its mutable counterpart) is part of a class cluster (read more about them here, under the 'Class Cluster' heading), and should not be subclassed because it causes problems such as what you've mentioned (read the subclassing notes in the NSDictionary Class Reference). Whatever it is you need to do, you're going to have a way to extend the classes you want to use in order to do what you want to do. For instance, the above code can easily be placed in a category (read more about categories here).
Are you sure you are not getting the exception when passing in "nil" for a KEY (not a value)?
Related
I have a nested NSMutableDictionary and am successfully pulling out a value at some 'depth' nested in other dictionaries, like:
NSNumber *num = [myDictionary valueForKeyPath:#"league.team.away.score"];
All is good. And I can confirm that all dictionaries at all levels are mutable.
But... what if that key path does not exist?
As expected, I get an NSUndefinedKeyException. I tried a fix with a try/catch tactic, to no avail.
Apple's solution to this concerns overriding valueForUndefinedKey:
"Subclasses can override this method to return an alternate value for undefined keys. The default implementation raises an NSUndefinedKeyException."
Great... so I create a subclass NSMutableDictionaryMod, then I get a complaint...
[NSMutableDictionary initWithCapacity:] method only defined for abstract class. Define -[NSMutableDictionaryMod initWithCapacity:]!
I go define said initializer (and can confirm it gets there in Xcode), but boom it crashes on the self = [super initWithCapacity:numItems]; line.
Then I noticed this Apple gem on NSMutableDictionary:
There should typically be little need to subclass NSMutableDictionary. If you do need to customize behavior, it is often better to consider composition rather than subclassing.
Any suggestions would be appreciated.
I have a subclass of NSMutableArray which in fact deals with a certain type of data i.e. say Employee. The problem is I don't like the inherented names of addObject insertObject and etc. and want to change them to something like addEmployee insertEmployee.
How should I deal with this?
If you are not going to inherit the methods of the superclass then you should not use that superclass!
When you inherit it is a 'is a' relationship between the sub and super classes. "Employer is a NSMutableArray" - no, that is not true and thus don't make Employer a subclass of NSMutableArray. Additionally, in the future you might use a dictionary to store employees (like mapping 'name' -> 'employee') and then having the representation being inherited as an array simply won't work.
#interface Employer : NSObject {
NSMutableArray *employees;
}
- (void) addEmployee: (Employee *) employee;
#end
Like such. Now addObject: isn't workable on instances of Employee; only addEmployee: works. Additionally, you'll only want to specialize methods like filteredArrayWithPredicate: eventually - so it won't be an advantage to inherit them.
You add a method addEmployee: and in that call addObject:. Similar for insertObject:
You can inherit NSMutableArray and add methods like -addEmployee: then add this in your .h file:
- (void)addObject:(id)anObject __attribute__((unavailable("Use -addEmployee:")));
This is a clang extension which will cause a complier error.
References:
How do I flag a function as being deprecated in an iPhone Objective C header file?
http://clang.llvm.org/docs/LanguageExtensions.html#messages-on-deprecated-and-unavailable-attributes
Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}
In a project I have taken on, the original author has opted to use objc_setAssociatedObject() and I'm not 100% clear what it does or why they decided to use it.
I decided to look it up and, unfortunately, the docs aren't very descriptive about its purpose.
objc_setAssociatedObject
Sets an associated value for a given object using a given key and association policy.
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
Parameters
object
The source object for the association.
key
The key for the association.
value
The value to associate with the key key for object. Pass nil to clear an existing association.
policy
The policy for the association. For possible values, see “Associative Object Behaviors.”
So what exactly does this function do and in what cases should it be used?
Edit after reading answers
So what is the point in the following code?
Device *device = [self.list objectAtIndex:[indexPath row]];
DeviceViewController *next = [[DeviceViewController alloc] initWithController:self.controller
device:device
item:self.rootVC.selectedItem];
objc_setAssociatedObject(device, &kDeviceControllerKey, next, OBJC_ASSOCIATION_RETAIN);
What is the point in associating the device with the view controller if it's already an instance variable?
objc_setAssociatedObject adds a key value store to each Objective-C object. It lets you store additional state for the object, not reflected in its instance variables.
It's really convenient when you want to store things belonging to an object outside of the main implementation. One of the main use cases is in categories where you cannot add instance variables. Here you use objc_setAssociatedObject to attach your additional variables to the self object.
When using the right association policy your objects will be released when the main object is deallocated.
From the reference documents on Objective-C Runtime Reference:
You use the Objective-C runtime
function objc_setAssociatedObject to
make an association between one object
and another. The function takes four
parameters: the source object, a key,
the value, and an association policy
constant. The key is a void pointer.
The key for each association must be unique. A typical pattern is to
use a static variable.
The policy specifies whether the associated object is assigned,
retained, or copied, and whether the
association is be made atomically or
non-atomically. This pattern is
similar to that of the attributes of
a declared property (see “Property
Declaration Attributes”). You specify
the policy for the relationship using
a constant (see
objc_AssociationPolicy and
Associative Object Behaviors).
Establishing an association between an array and a string
static char overviewKey;
NSArray *array =
[[NSArray alloc] initWithObjects:#"One", #"Two", #"Three", nil];
// For the purposes of illustration, use initWithFormat: to ensure
// the string can be deallocated
NSString *overview =
[[NSString alloc] initWithFormat:#"%#", #"First three numbers"];
objc_setAssociatedObject (
array,
&overviewKey,
overview,
OBJC_ASSOCIATION_RETAIN
);
[overview release];
// (1) overview valid
[array release];
// (2) overview invalid
At point 1, the string overview is
still valid because the
OBJC_ASSOCIATION_RETAIN policy
specifies that the array retains the
associated object. When the array is
deallocated, however (at point 2),
overview is released and so in this
case also deallocated. If you try to,
for example, log the value of
overview, you generate a runtime
exception.
Here is a list of use cases for object associations:
one: To add instance variables to categories. In general this technique is advised against, but here is an example of a legitimate use. Let's say you want to simulate additional instance variables for objects you cannot modify (we are talking about modifying the object itself, ie without subclassing). Let's say setting a title on a UIImage.
// UIImage-Title.h:
#interface UIImage(Title)
#property(nonatomic, copy) NSString *title;
#end
// UIImage-Title.m:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
static char titleKey;
#implementation UIImage(Title)
- (NSString *)title
{
return objc_getAssociatedObject(self, &titleKey);
}
- (void)setTitle:(NSString *)title
{
objc_setAssociatedObject(self, &titleKey, title, OBJC_ASSOCIATION_COPY);
}
#end
Also, here is a pretty complex (but awesome) way of using associated objects with categories.. it basically allows you to pass in a block instead of a selector to a UIControl.
two: Dynamically adding state information to an object not covered by its instance variables in conjunction with KVO.
The idea is that your object gains state information only during runtime (ie dynamically). So the idea is that although you can store this state info in an instance variable, the fact that you're attaching this info into a an object instantiated at runtime and dynamically associating it with the other object, you are highlighting the fact that this is a dynamic state of the object.
One excellent example that illustrates this is this library, in which associative objects are used with KVO notifications. Here is an excerpt of the code (note: this KVO notification isn't necessary to run make the code in that library work.. rather it's put there by the author for convenience, basically any object that registers to this will be notified via KVO that changes have happened to it):
static char BOOLRevealing;
- (BOOL)isRevealing
{
return [(NSNumber*)objc_getAssociatedObject(self, &BOOLRevealing) boolValue];
}
- (void)_setRevealing:(BOOL)revealing
{
[self willChangeValueForKey:#"isRevealing"];
objc_setAssociatedObject(self, &BOOLRevealing,
[NSNumber numberWithBool:revealing], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self didChangeValueForKey:#"isRevealing"];
}
bonus: take a look at this discussion/explanation of associated objects by Mattt Thompson, author of the seminal AFNetworking library
To answer your revised question:
What is the point in associating the device with the view controller if it's already an instance variable?
There are several reasons why you might want to do this.
the Device class doesn't have a controller instance variable, or property and you can't change it or subclass it e.g. you don't have the source code.
you want two controllers associated with the device object and you can't change the device class or subclass it.
Personally, I think it is very rare to need to use low level Objective-C runtime functions. This looks like a code smell to me.
Say I have the following Objective-C class:
#interface Foo {
int someNumber;
NSString *someString;
}
and for reasons I won't get into here, I want to use KVC to update, in a generic fashion, the values for those variables:
[f setValue:object forKey:#"someNumber"];
or
[f setValue:object forKey:#"someString"];`
If object is a string and I'm updating the someNumber variable, it seems that I need to know to use an NSNumberFormatter to get an NSNumber and then Cocoa automatically converts that to an int inside setValue:forKey:.
Is there any way to avoid this custom code and have Cocoa infer the conversion to an int from a string, or do I need to catch this situation each time and handle it myself?
The following code should handle the conversion automatically, as long as the object parameter is a member of a class that implements integerValue.
Note that both NSNumber and NSString implement this method.
- (void)setValue:(id)object forKey:(NSString *)key
{
if ([key isEqual:#"someNumber"])
{
someNumber = [object integerValue];
}
//...
}
edit: (side note):
You can avoid writing the method yourself by using objective-c properties. The default implementation of setValue:forKey: will do the work for you, as long as you create properties for someNumber and someString.
You should do the conversion yourself with a number formatter, it gives you finer control than anything that the framework might consider to be appropriate. It is also, probably, not a good idea to use a single instance of an object to update the values for your ivars. More appropriately, you could perform your update based on the class of the object (providing you are not storing in an id by querying the runtime as to the class of the object by means of object_getClassName. More information is available in the Objective-C 2.0 Runtime Reference. But in general, you will likely find bugs in your code as a result of doing things that way.
I'm with wisequark on this one. I think of setValue:forKey: as equivalent to calling the setter directly. If you had a method:
- (void)setSomeValue:(NSInteger)aValue
You wouldn't expect to be able to pass in an NSString and hope the method can figure it out.
Put another way, a key value gives you access to a property, and that property has a type. When using KVC, numbers just get wrapped in an NSNumber object so that there's only one setValue:forKey: method. Therefore, I say it's always the calling code's responsibility to package up the string in an NSNumber object.
It's times like these I lament that Cocoa Touch doesn't support bindings. Bindings would allow you to add a value transformer that could handle the transformation from string to number automatically for you. Maybe in the next version!