Next article: Friday Q&A 2010-02-05: Error Returns with Continuation Passing Style
Previous article: Friday Q&A 2010-01-22: Toll Free Bridging Internals
Tags: evil fridayqna objectivec override swizzling
It's that time of the week again. For this week's Friday Q&A Mike Shields has suggested that I talk about method replacement and method swizzling in Objective-C.
Overriding Methods
Overriding methods is a common task in just about any object oriented language. Most of the time you do this by subclassing, a time-honored technique. You subclass, you implement the method in the subclass, you instantiate the subclass when necessary, and instances of the subclass use the overridden method. Everybody knows how to do this.
Sometimes, though, you need to override methods that are in objects whose instantiation you don't control. Subclassing doesn't suffice in that case, because you can't make that code instantiate your subclass. Your method override sits there, twiddling its thumbs, accomplishing nothing.
Posing
Posing is an interesting technique but, alas, is now obsolete, since Apple no longer supports it in the "new" (64-bit and iPhone) Objective-C runtime. With posing, you subclass, then pose the subclass as its superclass. The runtime does some magic and suddenly the subclass is used everywhere, and method overrides become useful again. Since this is no longer supported, I won't go into details.
Categories
Using a category, you can easily override a method in an existing class:
@implementation NSView (MyOverride)
- (void)drawRect: (NSRect)r
{
// this runs instead of the normal -[NSView drawRect:]
[[NSColor blueColor] set];
NSRectFill(r);
}
@end
- It's impossible to call through to the original implementation of the method. The new implementation replaces the original, which is simply lost. Most overrides want to add functionality, not completely replace it, but it's not possible with a category.
- The class in question could implement the method in question in a category too, and the runtime doesn't guarantee which implementation "wins" when two categories contain methods with the same name.
Using a technique called method swizzling, you can replace an existing method from a category without the uncertainty of who "wins", and while preserving the ability to call through to the old method. The secret is to give the override a different method name, then swap them using runtime functions.
First, you implement the override with a different name:
@implementation NSView (MyOverride)
- (void)override_drawRect: (NSRect)r
{
// call through to the original, really
[self override_drawRect: r];
[[NSColor blueColor] set];
NSRectFill(r);
}
@end
override_drawRect:
is actually the original!
To swap the method, you need a bit of code to move the new implementation in and the old implementation out:
void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
For the case where the method only exists in a superclass, the first step is to add a new method to this class, using the override as the implementation. Once that's done, then the override method is replaced with the original one.
The step of adding the new method can also double as a check to see which case is actually present. The runtime function class_addMethod
will fail if the method already exists, and so can be used for the check:
if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)))
{
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}
else
{
method_exchangeImplementations(origMethod, overrideMethod);
}
}
method_exchangeImplementations
call just uses the two methods that the code already fetched, and you might wonder why it can't just go straight to that and skip all of the annoying stuff in the middle.
The reason the code needs the two cases is because class_getInstanceMethod
will actually return the Method
for the superclass if that's where the implementation lies. Replacing that implementation will replace the method for the wrong class!
As a concrete example, imagine replacing -[NSView description]
. If NSView
doesn't implement -description
(which is probable) then you'll get NSObject
's Method
instead. If you called method_exchangeImplementations
on that Method
, you'd replace the -description
method on NSObject
with your own code, which is not what you want to do!
(When that's the case, a simple category method would work just fine, so this code wouldn't be needed. The problem is that you can't know whether a class overrides a method from its superclass or not, and that could even change from one OS release to the next, so you have to assume that the class may implement the method itself, and write code that can handle that.)
Finally we just need to make sure that this code actually gets called when the program starts up. This is easily done by adding a +load
method to the MyOverride
category:
+ (void)load
{
MethodSwizzle(self, @selector(drawRect:), @selector(override_drawRect:));
}
This is a bit complicated, though. The swizzling concept is a little weird, and especially the way that you call through to the original implementation tends to bend the mind a bit. It's a pretty standard technique, but I want to propose a way that I believe is a little simpler, both in terms of being easier to understand and easier to implement.
It turns out that there's no need to preserve the method-ness of the original method. The dynamic dispatch involved in [self override_drawRect: r]
is completely unnecessary. We know which implementation we want right from the start.
Instead of moving the original method into a new one, just move its implementation into a global function pointer:
void (*gOrigDrawRect)(id, SEL, NSRect);
+load
you can fill that global with the original implementation
+ (void)load
{
Method origMethod = class_getInstanceMethod(self, @selector(drawRect:));
gOrigDrawRect = (void *)method_getImplementation(origMethod);
void *
for these things just because it's so much easier to type than long, weird function pointer types, and thanks to the magic of C, the void *
gets implicitly converted to the right pointer type anyway.)
Next, replace the original. Like before, there are two cases to worry about, so I'll first add the method, then replace the existing one if it turns out that there is one:
if(!class_addMethod(self, @selector(drawRect:), (IMP)OverrideDrawRect, method_getTypeEncoding(origMethod)))
method_setImplementation(origMethod, (IMP)OverrideDrawRect);
}
static void OverrideDrawRect(NSView *self, SEL _cmd, NSRect r)
{
gOrigDrawRect(self, _cmd, r);
[[NSColor blueColor] set];
NSRectFill(r);
}
The Obligatory Warning
Overriding methods on classes you don't own is a dangerous business. Your override could cause problems by breaking the assumptions of the class in question. Avoid it if it's at all possible. If you must do it, code your override with extreme care.
Conclusion
That's it for this week. Now you know the full spectrum of method override possibilities in Objective-C, including one variation that I haven't seen discussed much elsewhere. Use this power for good, not for evil!
Come back in seven days for the next edition. Until then, keep sending in your suggestions for topics. Friday Q&A is powered by reader submissions, so if you have an idea for a topic to cover here, send it in!
Comments:
The runtime-function class_replaceMethod() takes care of that, if the method is defined in the class, then the replacement is done, if it's defined in a super-class then the function adds the method to the class. So you simply need to retrieve the "old" implementation (that might be the super-class's implementation) and call it in your function replacement.
+load
method can be cut down to just this:
Method origMethod = class_getInstanceMethod(self, @selector(drawRect:));
gOrigDrawRect = (void *)class_replaceMethod(self, @selector(drawRect:), (IMP)OverrideDrawRect, method_getTypeEncoding(origMethod))
mouseEntered:
mouseExited:
mouseMoved:
If I remember correctly, these methods all use an IMP that actually determines what to do based on the _cmd argument. Therefore, when you swizzle, you end up passing override_mouseEntered: as _cmd instead of a value that it knows how to handle.
The direct override should not suffer from this problem since it's passing on _cmd correctly.
_cmd
, but never came across a place where it mattered in practice. Interesting!Warning: extreme hacking inside!
But yes, the supersequent method stuff is a hack for the same reason any "undocumented" stuff is: it can be gone at a moment's notice. It relies on the way that things just happen to be done.
As for multiple categories though... the way the ObjC 1.0/2.0 runtimes just happen to be written, all categories are preserved in the order that they are loaded. Technically load order is deterministic but it's fragile -- generally, the system libraries will be loaded before your code, but not always.
But don't ship code with it unless you want to get burned.
Sorry to have gotten in the way. I'll see myself out...
Your macro looks like this:
#define invokeSupersequent(...) \
([self getImplementationOf:_cmd \
after:impOfCallingMethod(self, _cmd)]) \
(self, _cmd, ##__VA_ARGS__)
-getImplementationOf:
is defined to return an IMP
, which takes variable arguments after the self
and _cmd
parameters. This macro does not cast the IMP
to a different function pointer type (and indeed could not, as it doesn't have enough information to do so). This means that the IMP
is being called with variable argument calling conventions. Or did I miss some place where everything gets cast to the right function pointer type before calling?However, this is the calling convention used by objc_msgSend, and by extension all methods except the objc_msgSend_(st/fp/fp2)ret methods. But yes, (st/fp/fp2)ret methods require a correct cast of the IMP or other special handling to work. Fortunately, the compiler is smart enough to give a hard error if you try to do this without a cast -- it doesn't slip through unnoticed.
I find the bigger problem is that without a signature, all regular parameters need to be correctly typed or they won't be passed correctly (since the compiler can infer the wrong register or stack size). This can cause problems without so much as a warning if you're not careful.
float
, short
, or char
(or the unsigned counterparts of the last two) through a vararg function, because they get promoted to double
and int
. This code illustrates the problem:
void Tester(int ign, float x, char y)
{
printf("float: %f char: %d\n", x, y);
}
int main(int argc, char **argv)
{
float x = 42;
float y = 42;
Tester(0, x, y);
void (*TesterAlt)(int, ...) = (void *)Tester;
TesterAlt(0, x, y);
return 0;
}
On my computer, the second invocation prints
float: 0.000000 char: 0
.
objc_msgSend
doesn't use vararg calling conventions. The convention it "uses" is any convention which is compatible with a pointer return value, and placing the first two arguments in a place where they can be expected. objc_msgSend
completely ignores all remaining arguments, and lets them pass through unhindered. The caller and the eventual callee (after objc_msgSend
looks it up and jumps to it) still have to agree on how those work, and if the caller thinks they're varargs and the callee doesn't, they won't get along.
You must cast all calls to
objc_msgSend
and its variants in order to have the compiler generate the correct code. Failing to do so will work for many cases, but only because you're getting lucky. The same goes for casting IMP
s.main
, y
should be of type char
. Still fails as described with that change made.
I forgot to mention: even if none of your parameters are of the offending types, there's still nothing which guarantees that the calling conventions will match between a vararg call with certain argument types and a non-vararg receiver with those same types. It's far more likely to work (and I think the ABIs of the platforms that OS X runs on may guarantee it on those particular platforms) but still unsafe.
in 32bit, the code will run as i expected, but when in 64bit, i get this error:
Error loading XXX: dlopen(XXX, 123): Symbol not found: _OBJC_CLASS_$_SomeClass
Referenced from: XXX
Expected in: flat namespace
in XXX
i googled for solutions, and i get some answers like:
http://groups.google.com/group/f-script/browse_thread/thread/09f07a3771032de4
i go and see the code, which does not fix the error.
i use JRSwizzle's + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_
thx :)
Example: Application has classes Dog and Mammal. Mammal has a reproduceWith: method. MadScientist uses your Direct Override technique to hook -[Dog reproduceWith:] and inject his additional code. EvilGeneticist uses any other hook technique to hook -[Mammal reproduceWith:].
Later, -[Dog reproduceWith:] is called; only MadScientist's hook is runs instead of both MadScientist and EvilGeneticist's hooks.
This is not a theoretical problem--the iPhone jailbreak community has encountered this issue numerous times and has standardized on two libraries: MobileSubstrate emits ARM bytecode at runtime to avoid it, and CaptainHook avoids it through macro trickery.
class_getInstanceMethod
will return the superclass's implementation if the class in question doesn't have one of its own, so everything still works as desired.I think, though, that you should have mentioned that in order to use the class_...() and method_...() functions, one needs to include the libobjc.A.dylib library in the Xcode Target and then #import <objc/runtime.h> in the source file.
Method origMethod = class_getInstanceMethod(self, @selector(drawRect:));
gOrigDrawRect = (void *)method_getImplementation(origMethod);
class_replaceMethod(self, @selector(drawRect:), (IMP)OverrideDrawRect, method_getTypeEncoding(origMethod))
- (void)copyToPrivatePasteboard:(id)sender
{
UIPasteboard *privatePasteboard = [self getPrivatePasteboard];
[privatePasteboard setString:@""];//How to get the copied string to store in pasteboard.
}
How can i write copied string to pasteboard. The parameter i am getting is of type id. If i convert it to NSString, it won't be proper because it is the sender who is calling this method (UIMenuController).
i'm sure you are aware that nothing get's "converted" here, in C a pointer is a pointer, 4 or 8 bytes are copied, that's all.
(not to be confused by the real magic Objective C can do converting types on the fly when setting them, setting a BOOL or float from a NSNumber for example, using setValueForKey.
In any case, the type gets converted, even if the value remains the same.
class_replaceMethod
does not return the imp of the superclass method. If the method was newly added to the class, gOrigDrawRect
is 0.
In that case, I add an extra
method_getImplementation
, using the method I get from class_getInstanceMethod
. That should return the nearest superclass IMP of the method.
Method origMethod= class_getClassMethod(self, @selector(foo));
class_replaceMethod(object_getClass(self), @selector(foo), (IMP)overrideFoo, method_getTypeEncoding(origMethod));
I'm assuming that is because class_getClassMethod is expecting to be working with Class level methods while class_replaceMethod could work with either. Is this correct?
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
http://github.com/rentzsch/jrswizzle
It takes care of most of the mindless busywork and edge cases.