Safe method swizzling done right.
Classical method swizzling with method_exchangeImplementations
is quite simple, but it has a lot of limitations:
- It is safe only if swizzling is done in the
+load
method. If you need to swizzle methods during application's lifetime you should take into account that third-party code may do swizzling of the same method in another thread at the same time. - The swizzled method must be implemented by the class itself and not by superclasses. Workarounds by copying implementation from the superclass do not really work. Original implementation in the superclass must be fetched at the time of calling, not at the time of swizzling (1,2).
- The swizzled method implementation must not rely on the
_cmd
argument. (And generally you can not be sure in it (5).) - Naming conflicts are possible (3).
For more details see discussions in: 1, 2, 3, 4, 5.
RSSwizzle avoids all these known pitfalls.
Original implementation must always be called from the new implementation. And because of the the fact that for safe and robust swizzling original implementation must be dynamically fetched at the time of calling and not at the time of swizzling (1,2), swizzling API is slightly unusual.
Example for swizzling -(int)calculate:(int)number;
method:
RSSwizzleInstanceMethod(classToSwizzle,
@selector(calculate:),
RSSWReturnType(int),
RSSWArguments(int number),
RSSWReplacement(
{
// The following code will be used as the new implementation.
// Calling original implementation.
int res = RSSWCallOriginal(number);
// Returning modified return value.
return res + 1;
}), 0, NULL);
Alternatively you may use an API without macros, though it is a little bit complicated.
You should pass a factory block that returns the block for the new implementation of the swizzled method. And use swizzleInfo
argument to retrieve and call original implementation.
Example for swizzling -(int)calculate:(int)number;
method:
SEL selector = @selector(calculate:);
[RSSwizzle
swizzleInstanceMethod:selector
inClass:classToSwizzle
newImpFactory:^id(RSSwizzleInfo *swizzleInfo) {
// This block will be used as the new implementation.
return ^int(__unsafe_unretained id self, int num){
// You MUST always cast implementation to the correct function pointer.
int (*originalIMP)(__unsafe_unretained id, SEL, int);
originalIMP = (__typeof(originalIMP))[swizzleInfo getOriginalImplementation];
// Calling original implementation.
int res = originalIMP(self,selector,num);
// Returning modified return value.
return res + 1;
};
}
mode:RSSwizzleModeAlways
key:NULL];
Class method swizzling is done with a similar API.
Example for swizzling +(int)calculate:(int)number;
method:
RSSwizzleClassMethod(classToSwizzle,
@selector(calculate:),
RSSWReturnType(int),
RSSWArguments(int number),
RSSWReplacement(
{
// Calling original implementation.
int res = RSSWCallOriginal(number);
// Returning modified return value.
return res + 1;
}));
Swizzling frequently goes along with checking whether this particular class (or one of its superclasses) has been already swizzled. Here the mode
and key
parameters can help.
Possible mode values:
RSSwizzleModeAlways
RSSwizzle always does swizzling regardless of the givenkey
.RSSwizzleModeOncePerClass
RSSwizzle does not do swizzling if the same class has been swizzled earlier with the samekey
.RSSwizzleModeOncePerClassAndSuperclasses
RSSwizzle does not do swizzling if the same class or one of its superclasses have been swizzled earlier with the samekey
.
Here is an example of swizzling -(void)dealloc;
only in case when neither class and no one of its superclasses has been already swizzled with the given key
:
static const void *key = &key;
SEL selector = NSSelectorFromString(@"dealloc");
RSSwizzleInstanceMethod(classToSwizzle,
selector,
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
NSLog(@"Deallocating %@.",self);
RSSWCallOriginal();
}), RSSwizzleModeOncePerClassAndSuperclasses, key);
Note:
RSSwizzleModeOncePerClassAndSuperclasses
mode does not guarantees that your implementation will be called only once per method call. If the order of swizzling is: first inherited class, second superclass; then both swizzlings will be done and the new implementation will be called twice.
RSSwizzle is fully thread safe. You do not need any additional synchronization.
Add RSSwizzle
to your Podfile.
- iOS 5.0+
- Mac OS X 10.7+
- ARC
Yan Rabovik (@rabovik on twitter)
MIT License.