views:

164

answers:

4

Hi

Is there a way to provide a method implementation (that bears the exact same name of a method defined by the framework) only when the method isn't already defined in the system? For example method [NSSomeClass someMethod:] exists only in Mac OS X 10.6 and if my app runs in 10.5, I will provide that method's definition in a category. But when the app runs in 10.6, I want the OS-provided method to run.

Background: I'm creating an app targeted for both 10.5 and 10.6. The problem is that I recently realized that method +[NSSortDescriptor sortDescriptorWithKey:ascending:] only exists in 10.6 and my code is already littered by that method call. I could provide a default implementation for it (since this time it's not too difficult to implement it myself), but I want the "native" one to be called whenever my app runs on 10.6. Furthermore if I encounter similar problems in the future (with more difficult-to-implement-myself methods), I might not be able to get away with providing a one-liner replacement.

This question vaguely similar to Override a method via ObjC Category and call the default implementation? but the difference is that I want to provide implementations only when the system doesn't already have one.

Thanks.

+4  A: 

Yes, this is possible. Since you are targetting 10.5+ I'm assuming you are using the ObjC2 runtime, which makes it fairly straightforward.

The Objective-C Runtime Reference has all the methods you will need. Specifically, you can use class_getClassMethod or class_getInstanceMethod to see if the method already exists, and then class_addMethod to bind your implementation to that selector if the class doesn't already have it.

smorgan
+1  A: 

Alternatively, you could just do a find and sub -[NSSortDescriptor initWithKey:ascending:] then add the appropriate release statement.

It's more cumbersome to implement but far less fragile and error prone than altering the class itself. That is especially true if you've never done that before. You'll probably spend more time coming up to speed on the override than you would just doing the find.

TechZen
+7  A: 

I would compile +[NSSortDescriptor sortDescriptorWithKey:ascending:] in a category into a separate bundle. Then at the very beginning of your main, check if the NSSortDescriptor class has the sortDescriptorWithKey:ascending: method with respondsToSelector:. If it's not implemented (i.e. you are running on < 10.6), then load the bundle with -[NSBundle loadAndReturnError:].

This way, you will run the OS-provided method on 10.6 and your implementation on 10.5.

0xced
+1 clever approach :)
Dave DeLong
Agreed. Very nice indeed.
Rob Keniger
A: 

think about compromise between changing initWithKey:ascending: method and and adding new method at runtime - just subclass NSSortDescriptor and replace all NSSortDescriptor calls with NSMySortDescriptor;

//NSMySortDescriptor.h
@interface NSMySortDescriptor : NSSortDescriptor {
}
- (id)initWithKey:(NSString *)keyPath ascending:(BOOL)ascending
@end

//NSMySortDescriptor.m

@implementation NSMySortDescriptor
- (id)initWithKey:(NSString *)keyPath ascending:(BOOL)ascending{
   // check if super i.e. has initWithKey:ascending: method
   if( [NSSortDescriptor instancesRespondToSelector:@selector(initWithKey:ascending:)] ) {
      [NSSortDescriptor initWithKey:ascending:];
   }
   else{
     // your custom realization for Mac OS X 10.5
     //...
   }
}
@end
Vladimir
`NSSortDescriptor` will never respond to `+initWithKey:ascending:`, because (by convention) class methods are not supposed to use `init` in their names.
Dave DeLong
Oh yes, my mistake - have taken it from original question code. Certainly [[NSSortDescriptor class] respondsToSelector:@selector(sortDescriptorWithKey:ascending:)];or Oh yes, my mistake - take it from original question code. Certainly [NSSortDescriptor instancesRespondToSelector:@selector(initWithKey:ascending:)]; - thanks;
Vladimir