I want to add a selector into a dictionary (the main purpose is for identifying the callback method and delegate after finish doing something)
But I find that I can not do that, the program will get an error "EXC_BAD_ACCESS".
Are there any other way for me to add that method selector to a dictionary?
Thanks for your help.
I know this question was answered a long time ago, but just in case anyone stumbles upon it like I did...
The combination of NSStringFromSelector and NSSelectorFromString as answered above is probably the best way to go. But if you really want to, you can use a selector as a value or key in an NSDictionary.
A selector (type SEL) is implemented as a pointer to a struct in Apple's Objective-C runtimes. A pointer cannot be used directly in a dictionary, but a pointer can be wrapped in an NSValue object that can be used.
Using this method you can store a selector as a value in a dictionary using code like this:
dictionary =
[NSDictionary dictionaryWithObject:[NSValue valueWithPointer:selector]
forKey:key];
A selector can be retrieved using code like this:
SEL selector = [[dictionary objectForKey:key] pointerValue];
Similarly for using a selector as a key:
dictionary =
[NSDictionary dictionaryWithObject:value
forKey:[NSValue valueWithPointer:selector]];
value = [dictionary objectForKey:[NSValue valueWithPointer:selector]];
Adding a new entry to a dictionary does two things (in addition to adding it to the dictionary, obviously):
It takes a copy of the key value. This means that the the key object must implement the NSCopying protocol
retains the value. This means that it needs to implement the NSObject protocol
It's probably the second that's causing your EXC_BAD_ACCESS.
There are at least two ways around this.
Firstly, rather than adding the selector you could add the instance of the class that implements the selector to your dictionary. Usually your class will inherit from NSObject and it will work fine. Note that it will retain the class though, maybe not what you want.
Secondly, you can convert a selector to a string (and back again) using NSSelectorFromString and NSStringFromSelector (docs are here).
I get my answer based on the comment of Zydeco:
You can convert between SEL and
NSString using NSSelectorFromString
and NSStringFromSelector
The common idiom in Obj-C is to have specific names for callbacks for specific events. (Such parserDidBeginDocument: from NSXMLParserDelegate). If you really need to be able to specify the names, it is likely that your only recourse is to add the names of the selectors as #"mySelector:withArgument:context:" or somesuch.
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.
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"];
}
I'm working on iPhone and I'm using navigation.
I have list of data in RootViewController, and pass one data to detailViewController when a cell is clicked. Like this,
detailViewController.message = [tableData objectAtIndex:indexPath.row];
And I want to modify the content of 'message' in detailViewController. Is that possible?
I've tried that, but I get an error that it's immutable object. How can I do that? Somebody give me a hint. Thanks ;)
Added ------------------------------------------------
Ok. I'll specify the question.
in detailViewController, the message is decleared NSMutableDictionary* type.
and used like this.
NSMutableString *str = [m_message objectForKey:KEY_CONTENT];
[str appendFormat:#"appended!"];
And I've got this message.
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'
Can't I just modify it's content like modifying data in c++ using pointer?
I think it would be best to use a delegate pattern for this. Create a DetailViewControllerDelegate protocol that informs its delegate of a changed message. Something along the lines of:
-(void)detailViewController:(DetailViewController *)controller didChangeMessage:(NSString *)message;
You could also directly manipulate the object by using a mutable one (NSMutableString, NSMutableArray etc.), but using a delegate improves reusability and decouples your classes.
EDIT:
The fact that your dictionary is mutable doesn't mean the object inside it are mutable as well. Your string str is probably immutable. Since NSMutableString is a subclass of NSString, the assignment will work though. You should make sure that you put a mutable string in the dictionary, or use the mutableCopy method of NSString to get a mutable copy of it.
It depends on what type of object you are passing. Immutable means that you cant alter the object. If you for exaple use NSArray, you have to switch to NSMutableArray instead. The same for string or dictionary.
Okay, i think i figured it out. The documentation for appendFormat says that appendFormat is for appending objects, just like when you use stringWithFormat.
The documentation states that:
The appended string is formed using
NSString's stringWithFormat: method
with the arguments listed.
You should use appendString to get the result you want.
I want to populate [MyClass class] from a JSON string.
I use json-framework to get the NSDictionary, and it's dead easy to instantiate and setValue: forKey:... on my data object.
But for more complex data objects with classes as members of MyClass,
ie:
MyOtherClass *classNoTwo
I tried with
Class test = object_getClass(myClass.classNoTwo);
id foo = [[test alloc] init];
But foo is nil/null. The only way I found to get around it is to in my init method of MyClass is to alloc memory for it, and later replace it.
I would also like to know how to get rid of the myClass.classNoTo reference, as I am not supposed to know all the properties in my general parser.
Please don't just tell me to read the documentation, because I've done that, and I can't figure it out.
Thanks in advance
Try calling class_getProperty() to access a property of a particular name and then property_getAttributes() on the property returned by the first function. This will return a string that encodes the property's attributes, including the type. The format of the string is explained in Property Type Strings in the documentation.
Finally, when you have derived the type of the property, use NSClassFromString() to instantiate an object.
Also see the docs for the two functions mentioned for more details.
I have written a very simple dependency injection container called Factory. I do not get your question entirely, but the problems we solve look similar. Take a look at my sources, they are very simple and should get you started. You might be interested especially in the ClassAnalyzer class. It’s just a hack, but you should be able to get what you want from there.
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!