Using Super in an Objective C Category? - objective-c

I'd like to override a method in an Objective C class that I don't have the source to.
I've looked into it, and it appears that Categories should allow me to do this, but I'd like to use the result of the old method in my new method, using super to get the old methods result.
Whenever I try this though, my method gets called, but "super" is nil... Any idea why? I'm doing iPhone development with the XCode 2.2 SDK. I'm definitely working with an instance of a class, and the method of the class is an instance method.
#implementation SampleClass (filePathResolver)
-(NSString*) fullPathFromRelativePath:(NSString*) relPath
{
NSString *result = [super fullPathFromRelativePath: relPath];
... do some stuff with the old result
return result;
}
Note and clarification: From what I can see in the Apple Docs, it appears to me that this should be allowed?
Categories docs at developer.apple.com:
When a category overrides an inherited method, the method in the
category can, as usual, invoke the
inherited implementation via a message
to super. However, if a category
overrides a method that already
existed in the category's class, there
is no way to invoke the original
implementation.

Categories extend the original class, but they don't subclass it, therefore a call to super doesn't find the method.
What you want is called Method Swizzling. But be aware that your code could break something. There's an article on Theocacao written by Scot Stevenson about Method Swizzling in the old Objective-C runtime, Cocoa with Love by Matt Gallagher has an article about Method Swizzling in the new Objective-C 2.0 runtime and a simple replacement for it.
Alternatively, you could subclass the class and then either use the subclass or use + (void)poseAsClass:(Class)aClass to replace the superclass. Apple writes:
A method defined by a posing class
can, through a message to super,
incorporate the superclass method it
overrides.
Be aware that Apple has deprecated poseAsClass: in Mac OS X 10.5.

If you will be coding against this class, simply rename the selector to something your code can use, and call the original selector on self:
#implementation SampleClass (filePathResolver)
-(NSString*) myFullPathFromRelativePath:(NSString*) relPath
{
NSString *result = [self fullPathFromRelativePath: relPath];
... do some stuff with the old result
return result;
}
If you want to override the default implementation of this selector for that class, you'll need to use the method swizzling approach.

Not exactly in category but there is a workaround by adding the method dynamically at runtime. Samuel Défago in his article describes a neat way to create block IMP implementation calling super, his original article can be found here
The relevant code is:
#import <objc/runtime.h>
#import <objc/message.h>
const char *types = method_getTypeEncoding(class_getInstanceMethod(clazz, selector));
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) {
struct objc_super super = {
.receiver = self,
.super_class = class_getSuperclass(clazz)
};
id (*objc_msgSendSuper_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper;
return objc_msgSendSuper_typed(&super, selector, argp);
}), types);

Related

Can subclass override non-public methods

I have two classes: BatchDownloader, SpeechDownlader
BatchDownloader is the base class, and SpeechDownloader inherited it.
In BatchDownloader, whenever one file was downloaded, -(void)downloadComplete:task will be called.
But in SpeechDownloader, I also want to post a notification in downloadComplete:task.
Can I just write the method with the same name in SpeechDownloader's implementation ? or there is a better way ?
Thanks.
p.s. I don't want to make -(void)downloadComplete:task public, because it should only be called by itself.
If you implement a method in a subclass that has the same name as a private method in a superclass, your subclass method will be called on instances of your subclass.
i.e., if you implement a method in your superclass like this, without declaring it anywhere:
#implementation classA
- (void)doSomething {
NSLog("a");
}
Then, in your subclass implementation, implement a method with the same name:
#implementation subclassOfA
- (void)doSomething {
NSLog("b");
}
When you call doSomething on an instance of your subclass, the subclass implementation will be called instead of the superclass implementation, so the code in this example will result in "b" being printed to the console.
However, if you also want to access the superclass implementation of the method, you can use:
- (void)doSomething {
[super doSomething];
NSLog("b");
}
This will also call the superclass implementation. If you get a compile error (due to the method being private and super not appearing to implement it), you can use [super performSelector:#selector(doSomething)] instead to do exactly the same thing.
This happens because of the way the Objective-C runtime looks up method calls. Since these methods have exactly the same method signature (same name, return type and arguments [none]), they are considered equal, and the runtime always checks the class of the object before looking in superclasses, so it will find the subclass method implementation first.
Also, this means you can do this:
classA *test = [subclassOfA new];
[test doSomething];
And, surprise surprise, the console will print "b" (Or "a b" if you called the super implementation too).
If you implement the method with the same method signature it will be called faith your implementation, public or not.

Accessing a method in a super class when it's not exposed

In a subclass, I'm overriding a method that is not exposed in the super class. I know that I have the correct signature as it is successfully overriding the superclass implementation. However, as part of the the new implementation, I need to call the superclass's implementation from the subclass's implementation.
Because it's not exposed I have to invoke the method via a call to performSelector:
SEL superClassSelector = NSSelectorFromString(#"methodToInvoke");
[super performSelector:superClassSelector];
However, in my application this results in an infinite recursive loop where the subclass's implementation is invoked every time I try to invoke the superclass's implementation.
Any thoughts?
I realize this is an atypical situation but unfortunately there's no way to get around what I'm trying to do.
The way I've dealt with this is to re-declare your super class' interface in your subclass implementation file with the method you want to call from the subclass
#interface MySuperclass()
- (void)superMethodIWantToCall;
#end
#implementation MySubclass
- (void)whateverFunction {
//now call super method here
[super superMethodIWantToCall];
}
#end
I'm not sure if this is the best way to do things but it is simple and works for me!
This doesn't work because you're only sending performSelector:, not the selector you pass to that, to the superclass. performSelector: still looks up the method in the current class's method list. Thus, you end up with the same subclass implementation.
The simplest way to do this may be to just write in your own call to objc_msgSendSuper():
// Top level (this struct isn't exposed in the runtime header for some reason)
struct objc_super
{
id __unsafe_unretained reciever;
Class __unsafe_unretained superklass;
};
// In the subclass's method
struct objc_super sup = {self, [self superclass]};
objc_msgSendSuper(&sup, _cmd, other, args, go, here);
This can cause problems in the general case, as Rob Napier has pointed out below. I suggested this based on the assumption that the method has no return value.
One way to go is to create a category of your class in a separate file with the method you are trying to expose
#interface MyClass (ProtectedMethods)
- (void)myMethod;
#end
and on the .m
#implementation MyClass (ProtectedMethods)
- (void)myMethod {
}
#end
Then, import this category from your .m files, and you're good to go. It's not the prettiest thing, but it'll do the trick

Swizzling a single instance, not a class

I have a category on NSObject which supposed to so some stuff. When I call it on an object, I would like to override its dealloc method to do some cleanups.
I wanted to do it using method swizzling, but could not figure out how. The only examples I've found are on how to replace the method implementation for the entire class (in my case, it would override dealloc for ALL NSObjects - which I don't want to).
I want to override the dealloc method of specific instances of NSObject.
#interface NSObject(MyCategory)
-(void)test;
#end
#implementation NSObject(MyCategory)
-(void)newDealloc
{
// do some cleanup here
[self dealloc]; // call actual dealloc method
}
-(void)test
{
IMP orig=[self methodForSelector:#selector(dealloc)];
IMP repl=[self methodForSelector:#selector(newDealloc)];
if (...) // 'test' might be called several times, this replacement should happen only on the first call
{
method_exchangeImplementations(..., ...);
}
}
#end
You can't really do this since objects don't have their own method tables. Only classes have method tables and if you change those it will affect every object of that class. There is a straightforward way around this though: Changing the class of your object at runtime to a dynamically created subclass. This technique, also called isa-swizzling, is used by Apple to implement automatic KVO.
This is a powerful method and it has its uses. But for your case there is an easier method using associated objects. Basically you use objc_setAssociatedObject to associate another object to your first object which does the cleanup in its dealloc. You can find more details in this blog post on Cocoa is my Girlfriend.
Method selection is based on the class of an object instance, so method swizzling affects all instances of the same class - as you discovered.
But you can change the class of an instance, but you must be careful! Here is the outline, assume you have a class:
#instance MyPlainObject : NSObject
- (void) doSomething;
#end
Now if for just some of the instances of MyPlainObject you'd like to alter the behaviour of doSomething you first define a subclass:
#instance MyFancyObject: MyPlainObject
- (void) doSomething;
#end
Now you can clearly make instances of MyFancyObject, but what we need to do is take a pre-existing instance of MyPlainObject and make it into a MyFancyObject so we get the new behaviour. For that we can swizzle the class, add the following to MyFancyObject:
static Class myPlainObjectClass;
static Class myFancyObjectClass;
+ (void)initialize
{
myPlainObjectClass = objc_getClass("MyPlainObject");
myFancyObjectClass = objc_getClass("MyFancyObject");
}
+ (void)changeKind:(MyPlainObject *)control fancy:(BOOL)fancy
{
object_setClass(control, fancy ? myFancyObjectClass : myPlainObjectClass);
}
Now for any original instance of MyPlainClass you can switch to behave as a MyFancyClass, and vice-versa:
MyPlainClass *mpc = [MyPlainClass new];
...
// masquerade as MyFancyClass
[MyFancyClass changeKind:mpc fancy:YES]
... // mpc behaves as a MyFancyClass
// revert to true nature
[MyFancyClass changeKind:mpc: fancy:NO];
(Some) of the caveats:
You can only do this if the subclass overrides or adds methods, and adds static (class) variables.
You also need a sub-class for ever class you wish to change the behaviour of, you can't have a single class which can change the behaviour of many different classes.
I made a swizzling API that also features instance specific swizzling. I think this is exactly what you're looking for: https://github.com/JonasGessner/JGMethodSwizzler
It works by creating a dynamic subclass for the specific instance that you're swizzling at runtime.

Overloaded method in obj-c category: how to pass it to original class?

As I know, in my overloaded method (in category) I may call [super method] to pass it to original class.
In my case I don't have header file of class, so I write:
#interface OriginalClass : NSObject
#end
#interface OriginalClass (overloadMethod)
-(void) method;
#end
#implementation OriginalClass (overloadMethod)
-(void) method
{
// some my code
[super method]; // here is warning that "NSObject may not respond to '-method'
}
#end
So is it possible to pass method to OriginalClass correctly without having its header file? Maybe it would be better to look at method_setImplementation?
You're adding a category, not a subclass. When adding a category, think of it as tacking your own methods on to the existing class. Instead of sending messages to super (which in this case is NSObject), just send them to self. Imagine you're in the implementation of the class that you're adding a category to.
I'm not sure if you think you're subclassing or if you're trying to swizzle...
If i remember correctly, the intro to ObjC programming in the docs mentions that when you override a method with a category, you can no longer access the original method.
Your question is pretty much identical to the one here: How do I call the original function from the overloaded function in a category?
Note the quote from the docs in the first answer

Override a method via ObjC Category and call the default implementation?

When using categories, you can override implementation methods with your own like so:
// Base Class
#interface ClassA : NSObject
- (NSString *) myMethod;
#end
#implementation ClassA
- (NSString*) myMethod { return #"A"; }
#end
//Category
#interface ClassA (CategoryB)
- (NSString *) myMethod;
#end
#implementation ClassA (CategoryB)
- (NSString*) myMethod { return #"B"; }
#end
Calling the method "myMethod" after including the category nets the result "B".
What is the easiest way for the Category implementation of myMethod to call the original Class A myMethod? As near as I can figure out, you'd have to use the low level calls to get the original method hook for Class A and call that, but it seemed like there would be a syntactically easier way to do this.
If you want a hackish way to do this that involves mucking with the objective-c runtime you can always use method swizzling (insert standard disclaimers here.) It will allow you to store the different methods as arbitrariliy named selectors, then swap them in at runtime as you need them.
From comp.lang.objective-C FAQ listing: "What if multiple categories implement the same method? Then the fabric of the Universe as we know it ceases to exist. Actually, that's not quite true, but certainly some problems will be caused. When a category implements a method which has already appeared in a class (whether through another category, or the class' primary #implementation), that category's definition overwrites the definition which was previously present. The original definition can no longer be reached by the Objective-C code. Note that if two categories overwrite the same method then whichever was loaded last "wins", which may not be possible to predict before the code is launched."
From developer.apple.com: "When a category overrides an inherited method, the method in the category can, as usual, invoke the inherited implementation via a message to super. However, if a category overrides a method that already existed in the category's class, there is no way to invoke the original implementation"
Check out my article about a solution found on the Mac Developer Library:
http://codeshaker.blogspot.com/2012/01/calling-original-overridden-method-from.html
Basically, it's the same as the above Method Swizzling with a brief example:
#import <objc/runtime.h>
#implementation Test (Logging)
- (NSUInteger)logLength {
NSUInteger length = [self logLength];
NSLog(#"Logging: %d", length);
return length;
}
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod(self, #selector(length)), class_getInstanceMethod(self, #selector(logLength)));
}
#end
With the swizzling "helper" methods included in ConciseKit, you actually call the default implementation… weirdly enough.. by calling your SWIZZLED implementation..
You set it up in + (void) load, calling + (BOOL)swizzleMethod:(SEL)originalSelector with:(SEL)anotherSelector in:(Class)klass;, i.e.
[$ swizzleMethod:#selector(oldTired:)
with:#selector(swizzledHotness:) in:self.class];
and then in the swizzled method.. let's suppose it returns -(id).. you can do your mischief, or whatever reason you are swizzling in the first place… and then, instead of returning an object, or self, or whatnot..
return [self swizzledHotness:yourSwizzledMethodsArgument];
As explained here…
In this method, it looks like we're calling the same method again, causing and endless recursion. But by the time this line is reached the two method have been swapped. So when we call swizzled_synchronize we're actually calling the original method.
It feels and looks odd, but.. it works. This enables you to add endless embellishments to existing methods, and still "call super" (actually self) and reap the benefits of the original method's handiwork… even without access to the original source.