Using objc_msgSendSuper to invoke a class method - objective-c

I was going through and replacing #synthesized(self) locks w/ this method
void _ThreadsafeInit(Class theClassToInit, void *volatile *theVariableItLivesIn, void(^InitBlock)(void))
{
//this is what super does :X
struct objc_super mySuper = {
.receiver = (id)theClassToInit,
.super_class = class_getSuperclass(theClassToInit)
};
id (*objc_superAllocTyped)(struct objc_super *, SEL, NSZone *) = (void *)&objc_msgSendSuper;
// id (*objc_superAllocTyped)(id objc_super, SEL, NSZone *) = (void *)&objc_msgSend;
do {
id temp = [(*objc_superAllocTyped)(&mySuper /*theClassToInit*/, #selector(allocWithZone:), NULL) init];//get superclass in case alloc is blocked in this class;
if(OSAtomicCompareAndSwapPtrBarrier(0x0, temp, theVariableItLivesIn)) { //atomic operation forces synchronization
if( InitBlock != NULL ) {
InitBlock(); //only the thread that succesfully set sharedInstance pointer gets here
}
break;
}
else
{
[temp release]; //any thread that fails to set sharedInstance needs to clean up after itself
}
} while (*theVariableItLivesIn == NULL);
}
which while a bit more verbose exhibits significantly better performance in non-contested cases
along with this little macro (excuse poor formatting, it's very simple). To allow the block to be declared after the initial nil check, looks to help LLVM keep the "already initialized" path extremely fast. That's the only one I care about.
#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }
So initially implemented it using the commented out sections for objc_superAllocTyped (actually first using [theClassToInit allocWithZone:NULL], which was definitely the best approach :) ), which worked great until I realized that most of the singletons in the project had overridden allocWithZone to return the singleton method... infinite loop. So I figured using objc_msgSendSuper should sort it out quickly, but I get this error.
[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584
The error doesn't seem to be related to the actual problem, as...
(lldb) po 0x4f9584
$1 = 5215620 DataUtils
(lldb) print (BOOL)[$1 respondsToSelector:#selector(allocWithZone:)]
(BOOL) $2 = YES
So I'm definitely missing something... I compared to assembly generated by a [super allocWithZone:NULL] method in an empty class... almost identical except for the functions called have different names (maybe just using different symbols, no idea, can't read it that well).
Any ideas? I can use class_getClassMethod on the superclass and call the IMP directly, but I'm trying to be reasonable in my abuse of the runtime :)

Alright, this wasn't actually that tricky once I recalled that the meta class contains all of the method information for a Class instance obtained via -[self class] or +[self] -> thanks http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html
This error occurred because I was asking the runtime to look up the method in NSObject's set of instance methods, which obviously doesn't contain allocWithZone: . The mistake in the error log presumably originated because the receiver was a metaclass instance, and Apple has their interns implement error logs.
so while with a normal instance method call via objc_msgSendSuper, you would pass a metaclass instance as objc_super.super_class, to invoke a class method, the metaclass itself is needed (everything is one level up).
Example, and a diagram that helped me understand this - (http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html)
struct objc_super mySuper;
mySuper.receiver = theClassToInit; //this is our receiver, no doubt about it
//either grab the super class and get its metaclass
mySuper.super_class = object_getClass( class_getSuperclass( theClassToInit ) );
//or grab the metaclass, and get its super class, this is the exact same object
mySuper.super_class = class_getSuperclass( object_getClass( theClassToInit ) );
Then the message can be resolved correctly. Makes perfect sense now that I started paying attention :P
Anyways, now that I found my mistake I feel like I've leveled up my Objc runtime understanding. I was also able to fix an architectural mistake made two years ago by someone I never met without having to modifying and re-test dozens of classes across 3 projects and 2 static libraries (God I love Objective-C). Replacing the #synchronized construct with a simple function call also halved the compiled code size of those methods. As a bonus, all our singleton accessors are now (more) threadsafe, because the performance cost for doing so is now negligible. Methods which naively re-fetched the singleton object multiple times (or in loops) have seen a huge speedup now that they don't have to acquire and release a mutex multiple times per invocation. All in all I'm very happy it all worked as I'd hoped.
I made a "normal" Objective-C method for this on a category of NSObject, which will work for both instance and Class objects to allow you to invoke a superclass's implementation of a message externally. Warning: This is only for fun, or unit tests, or swizzled methods, or maybe a really cool game.
#implementation NSObject (Convenience)
-(id)performSelector:(SEL)selector asClass:(Class)class
{
struct objc_super mySuper = {
.receiver = self,
.super_class = class_isMetaClass(object_getClass(self)) //check if we are an instance or Class
? object_getClass(class) //if we are a Class, we need to send our metaclass (our Class's Class)
: class //if we are an instance, we need to send our Class (which we already have)
};
id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper; //cast our pointer so the compiler can sort out the ABI
return (*objc_superAllocTyped)(&mySuper, selector);
}
so
[self performSelector:#selector(dealloc) asClass:[self superclass]];
would be equivalent to
[super dealloc];
Carry on runtime explorers! Don't let the naysayers drag you into their land of handwaving and black magik boxes, it's hard to make uncompromisingly awesome programs there*.
*Please enjoy the Objective-C runtime responsibly. Consult with your QA team for any bugs lasting more than four hours.

Related

Proper way of method swizzling in objective-C

Currently experimenting with method swizzling in Objective-C and I have a question. I am trying to understand the proper way to method swizzle and after researching online I stumbled upon this NSHipster post:
http://nshipster.com/method-swizzling/
In the post the author has some method swizzling sample code. I am looking for someone to better explain to me what the author is doing.. In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations? My only theory on this is maybe there is some off chance that viewWillAppear: is not added to UIViewController's method dispatch_table yet. Particularly if maybe the category is loaded into memory first before UIViewController... Is this the reason why? It seems rather odd? Just looking for some more insight/clarity, thanks :)
In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations?
Your confusion is understandable as this logic is not explained clearly.
First ignore the fact that the example is a category on the specific class UIViewController and just consider the logic as though the category was on some arbitrary class, let's call that class TargetClass.
We'll call the existing method we wish to replace existingMethod.
The category, being on TargetClass, adds the swizzling method, which we'll call swizzlingMethod, to TargetClass.
Important: Note that the function to get an method, class_getInstanceMethod, will find the method in the supplied class or any of its superclasses. However the functions class_addMethod and class_replaceMethod only add/replace methods in the supplied class.
Now there are two cases to consider:
TargetClass itself directly contains an implementation of existingMethod. This is the easy case, all that needs to be done is exchange the implementations of existingMethod and swizzlingMethod, which can be done with method_exchangeImplementations. In the article the call to class_addMethod will fail, as there is already and existingMethod directly in TargetClass and the logic results in a call to method_exchangeImplementations.
TargetClass does not directly contain an implementation of existingMethod, rather that method is provided through inheritance from one of the ancestor classes of TargetClass. This is the trickier case. If you simply exchange the implementations of existingMethod and swizzlingMethod then you would be effecting (instances of) the ancestor class (and in a way which could cause a crash - why is left as an exercise). By calling class_addMethod the article's code makes sure there is an existingMethod in TargetClass - the implementation of which is the original implementation of swizzlingMethod. The logic then replaces the implementation of swizzlingMethod with the implementation of the ancestor's existingMethod (which has no effect on the ancestor).
Still here? I hope that makes sense and hasn't simply sent you cross-eyed!
Another exercise if you're terminally curious: Now you might ask what happens if the ancestor's existingMethod implementation contains a call to super... if the implementation is now also attached to swizzlingMethod in TargetClass where will that call to super end up? Will it be to implementation in ancestor, which would see the same method implementation executed twice, or to the ancestor's ancestor, as originally intended?
HTH
load is called when a class is added in obj-c runtime.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/clm/NSObject/load
So let's say if a UIViewController gets added in obj-c runtime which already contains viewWillAppear: but you want it to be replaced by another implementation. So first you add a new method xxxWillAppear:.
Now once xxxWillAppear: has been added in ViewController class, only then you can replace it.
But the author also said :
For example, let’s say we wanted to track how many times each view controller is presented to a user in an iOS app
so he is trying to demonstrate a case where an app might have many view controllers but you do not want to keep replacing for each ViewController the viewWillAppear: implementation. Once the point of viewWillAppear: has been replaced, then instead of adding, only the exchange will need to be done.
Perhaps source code of Objective C runtime might help :
/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, BOOL replace)
{
IMP result = nil;
rwlock_assert_writing(&runtimeLock);
assert(types);
assert(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = _method_getImplementation(m);
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)_calloc_internal(sizeof(*newlist), 1);
newlist->entsize_NEVER_USE = (uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdup(types);
if (!ignoreSelector(name)) {
newlist->first.imp = imp;
} else {
newlist->first.imp = (IMP)&_objc_ignored_method;
}
attachMethodLists(cls, &newlist, 1, NO, NO, YES);
result = nil;
}
return result;
}
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", NO);
rwlock_unlock_write(&runtimeLock);
return old ? NO : YES;
}
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", YES);
rwlock_unlock_write(&runtimeLock);
return old;
}
You can dig more if you want:
http://www.opensource.apple.com/source/objc4/objc4-437/

Instance variable initialized without using "init" method?

Iam in the first phase of Objective-C learning curve, so please bear with me :).
Iam reading Apple documentation and doing exercises there also. Ive come to a problem with initialization of particular object, because I get unexpected (In my opinion) behavior in my Xcode.
To the point (lets make it simple):
STEP 1:
I have declared simple variable from a my custom class named XYZPerson. It looks like this:
XYZPerson *randomPerson;
"If" check for initialized object returned out "Not Initialized" (as expected).
if(randomPerson == nil) {
NSLog(#"Random person is not initialized");
} else {
NSLog(#"Random person is initialized");
}
STEP 2:
I have allocated the memory for this object using "alloc" word on this variable. As I understand in this phase, memory for this variable gets allocated. It also returns the pointer?
Line of code looks like this:
XYZPerson *randomPerson = [XYZPerson alloc];
Checking for "nil" surprised me: (It executes "else" statement).
if(randomPerson == nil) {
NSLog(#"Random person is not initialized");
} else {
NSLog(#"Random person is initialized");
}
I can also send messages to this object and they are executed just fine, without calling "init" method.
How can this be? Am I missing something? How can "randomPerson" variable be initialized before calling "init" method on this instance? Why is "init" there in the first place then?
Furthermore, the accepted answer here Difference between ! and nil check on Objective-C object , says that ALL object are set to nil in the alloc method!
Iam coming from Java and there is one way of declaring an object using "new" keyword. Without using "new" the instance of object will be always "null", and calling methods on that instance will result "NULLPOINTEREXPECTION Error".
P.S Is "nil" in objective C, same as "NULL" in Java?
Whole project looks like this:
main method:
XYZPerson *randomPerson = [XYZPerson alloc];
if(randomPerson == nil) {
NSLog(#"Random person is not initialized");
} else {
NSLog(#"Random person is initialized");
}
**XYZ Person Class:**
#import "XYZPerson.h"
#implementation XYZPerson
-(void)sayHello {
//[self saySomething];
}
-(void)saySomething:(NSString *) greeting {
NSLog(#"%#", greeting);
}
#end
I post an answer additional to that one linked in the comment for two reasons:
A. Your Q is slightly different.
B. I do not confirm with the linked answer in details.
First of all to your additional Qs: Yes, nil is Objectice-C's NULL. But there are some differences:
A. In most cases (using ARC) a reference to a pointer (not the object itself) is initialized with nil.
XYZPerson *person; // Usually automatically set to nil.
B. A message to nil is allowed in Objective-C, does not throw an exception and returns NO, 0, 0.0, nil, or whatever the representation of null resp. zero (if it does not have a null representation like integers) is for that type. Sometimes this is done intentionally and you can rely on that.
Two your main Q:
After creating an instance object with +alloc all instance variable (ivars) are set to NO, 0, 0.0, nil, or whatever the representation of null resp. zero is for that type. You should not set such ivars explicitly to that value.
For example, if the instances of the class XYZPerson has an ivar for the name typed NSString*, the ivar will be nil. So, one might think that an naked -init does not have any meaning, because it does not take parameters and therefore does nothing. But you simply do not know: Maybe something else is done in -init. And, that's probably a surprise for a Java developer, -init returns an object reference, so you cannot know, whether -init replaces the instance object, for example for twin toning. So even you do not see any meaning in it, the first message to an instance object has to be init. (Yes, in many case you would not see any difference, but you do not know, whether there is one or not or will be in future. It is a part of the API contract, so do it.)
In disagree with the linked answer in one point:
Sending +new… instead of +alloc -init is the better way to do it.
A. If you use a simple +new it is correct that it sends +alloc -init in many cases. Therefore it is obvious that this is not better or worse than sending +alloc -init. It is what it does. You always have to have a look at the documentation, whether a naked initialization, using +new or +alloc -init is allowed. But in such a case you likely do not want to do a naked initialization, because it is meaningless.
B. Sometimes it is for the implementor of a class easier to receive a new message to the class object.

NSClassFromString() security concerns

I'm trying to create a factory class structure where my abstract base class, PDObject, instantiates an instance the proper subclass based on information passed to it in an NSDictionary. Here's my init method for PDObject:
- (id)initWithDictionary:(NSDictionary *)dictionary inEnvironment:(PDEnvironment *)environment {
NSString *className = [dictionary objectForKey:#"objectType"];
if (className) {
Class objectClass = NSClassFromString(className);
if ([objectClass isSubclassOfClass:[PDObject class]]) {
self = [[objectClass alloc] initWithDictionary:dictionary inEnvironment:environment];
} else {
NSLog(#"tried to instantiate an object of the wrong object type");
self = nil;
}
} else {
NSLog(#"tried to instantiate an object without an object type");
}
return self;
}
I'm wondering if anyone knows of any security concerns with this pattern. I'm worried that something malicious could be passed in in the dictionary and instantiate something unexpected. I have a check to make sure that it is a proper subclass of PDObject. Is there anything I should be concerned about here, or am I just being paranoid?
It is unlikely to be a security hole, but passing potentially random strings to runtime functions isn't really something the runtime is hardened against. The risk isn't instantiating random classes, but causing the app to potentially crash or execute random code.
In general, I wouldn't go beyond minimal effort. To that ends, I would suggest using NSScanner to scan the class name to see if it has any characters that are obviously out of bounds. I would think scanning for alphanumericCharacterSet would be sufficient.
Dynamism is good and I don't see anything particularly risky here. If you want to avoid crashes, you can check for the particular object a. not being nil (just in case) and b. responding to any selector you want to send it. Also note that whichever kind of protection you use, who wants to mock with your app will always be able to do so using library interposition (meet the infamous DYLD_INSERT_LIBRARIES environment variable) and the Objective-C runtime.

Where is the retain count stored for NSObjects in Objective C

I am curious about how retain/release work internally. On the face, it seems like there's an integer related to each instance of an NSObject, which gets increased and decreased when you call -retain and -release, respectively.
But taking a look at NSObject, the only instance variable it has is the isa variable, for determining its class type.
So where are retain counts for individual objects stored? Not that I'm going to muck around with it, but just for my own edification.
Is it stored with the NSObject, but hidden away in some Objective C implementation detail? If so, that seems like a bad design to me. One should be able to create their own root class and handle their own retain/release counting in a similar fashion (not that it's a good idea--one would have to have a very good reason not to use NSObject).
The storage location for the retain count depends on both the runtime in use and the class implementation.
For Apple's Objective-C runtime, you can figure out a lot by digging into the source code of the Objective-C runtime.
For example, if you're using ARC (and I think even if you're not), the reference counts for most objects are stored in hash tables. Have a look at the _objc_rootRetain function in runtime/objc-arr.mm. I don't know exactly why they did this. Perhaps it's a way of keeping retain counts together for better cache behavior (which is important under ARC because ARC adjusts retain counts more often than non-ARC code usually does).
However, some classes override retain and related methods and store the retain count elsewhere. For example, when debugging a memory leak I discovered that CALayer does this. Instead of using the runtime's normal retain count mechanism, a CALayer stores its retain count in a private C++ implementation object. This is rather frustrating because it means the Instruments Allocations instrument doesn't log retains and releases of CALayer objects.
We do not know exactly how the data is stored, but we can rule out a few options:
Private Implementation Variables
We can rule this out, simply because when we iterate through the iVars of the NSObject class, we see only one: isa, as shown through this program:
id object = [NSObject new];
Class meta = object->isa;
printf("class name: %s\n", class_getName(meta));
unsigned count;
Ivar *ivars = class_copyIvarList(meta, &count);
for (int i = 0; i < count; i++) {
printf("iVar: %s\n", ivar_getName(ivars[i]));
}
free(ivars);
And note that even private implementation properties exist in the class metdata.
Private Properties
We can also rule this out, as even private properties are exposed in the classes metadata, as shown by the following example, there are no properties for the NSObject class:
id object = [NSObject new];
Class meta = object->isa;
printf("class name: %s\n", class_getName(meta));
objc_property_t *properties = class_copyPropertyList(meta, &count);
for (int i = 0; i < count; i++) {
printf("property: %s\n", property_getName(properties[i]));
}
Associated Objects
This one is very hard to rule out, as there are no direct ways to get a list of all the associated objects. However, since the concept of associated objects is very new, and reference counting has been around forever, I say that this is not very likely.
CoreFoundation struct-mangling
This is my best guess. When you create a NSObject, it is a struct behind the scenes. What is to say that the actual NSObject data representation is something like this:
typedef struct CFObject {
int retainCount;
id isa;
} *CFObjectRef;
Then, when an object is created:
id object_createInstance(...)
{
CFObjectRef object = malloc(sizeof(struct CFObject));
...
return (id) (object + sizeof(object->retainCount));
}
int object_retainCount(id self)
{
CFObjectRef asObject = (CFObjectRef) (self - sizeof(asObject->retainCount));
return asObject->retainCount;
}
I cannot verify this however, as there are many other ways this could be done (a map of integers to objects, for example).
It doesn't sound like it, but just in case... if you're thinking of using retain count directly, don't.
As for implementation details, sessions at WWDC 2011 mentioned that under ARC, most of the reference counting implementation has moved into the ObjC runtime. Source for that is available, so you might be able to find out for yourself how it works. For manual reference counting, much of the ObjC behaviors are replicated in CoreFoundation and libdispatch, which are also open source -- if you're looking to implement a similar scheme yourself, those might prove educational.
In general, this is an implementation detail for the same reason many things are: encapsulation is good policy, especially for framework providers. You don't want users of a framework depending on implementation details because then you can't change your implementation without breaking their code.
Don't know if this would be relevant, but I've stumbled upon the blog post about higher order messages implementation in the Objective-C. There author implements the HOM object as a root class (i.e. not inherited from NSObject) and the implementation looks like this:
#interface HigherOrderMessage {
Class isa;
NSUInteger retainCount;
//not relevant to this question part
}
Then the retain count managing methods are implemented like this:
- (id)retain {
__sync_add_and_fetch(&retainCount, 1);
return self;
}
- (id)autorelease {
[NSAutoreleasePool addObject:self];
return self;
}
- (void)release {
if (__sync_sub_and_fetch(&retainCount, 1) == 0) {
[methodSignatureForSelector release];
[forward release];
object_dispose(self);
}
}
This code actually works, so although we do not know how exactly the retainCount is implemented in the Cocoa classes, it is certain that it could be implemented in some similar way.
For addition insight, check out http://www.mikeash.com/pyblog/friday-qa-2011-09-16-lets-build-reference-counting.html, where Mike Ash explores an alternative implementation like the one Apple uses.

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.