Object-c中的黑魔法——Method Swizzling

上一篇讲了Object-c的函数调用机制,对理解Method Swizzling有很大帮助。这一次来看一看Object-c中的黑魔法——Method Swizzling。

Method Swizzling介绍

什么是Method Swizzling?简单来讲,就是修改SEL与IMP之间的映射关系,达到修改最终执行方法的目的。Method Swizzling,听起来就有黑魔法的意味。

如何实现Method Swizzling

1.创建一个category

#import <Foundation/Foundation.h>  
@interface UIView (MyAdditions)   
@end  

2.创建一个swizzling函数

@implementation UIView (MyAdditions)

- (void)my_setFrame:(CGRect)frame {
    // custom function
    [self my_setFrame:frame];
}
@end

等等,这不是循环调用吗?别忘了,我们后面会施展黑魔法,改变IMP,调用的不再是自己,不会有循环调用的问题。

3.交换IMP

注意别忘记引入:#import <objc/runtime.h>

Method oriMethod =  class_getInstanceMethod([UIView class], @selector(setFrame:));  
Method myMethod = class_getInstanceMethod([UIView class], @selector(my_setFrame:));  
method_exchangeImplementations(oriMethod, myMethod); 

这样调用 [UIView setFrame:] 时,实际调用的是自定义的方法 [UIView my_setFrame:]。

Method Swizzling的利与弊

这里有非常好的答案:

http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c

其中给出了一个解决命名冲突的定义方式:

@implementation NSView (MyViewAdditions)
static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
   // do custom work
   SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
   [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}
@end

typedef IMP *IMPPointer;
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

总结

对于我个人而言,并不是非常喜欢这种黑魔法。感觉还是慎用为妙,如果使用,尽量仅在load中swizzling。另外,网上有人说使用了Method Swizzling无法通过appstore的审核,还没有验证过。 虽然不建议发布使用,但是其他地方还是有很多用武之地的,比如调试、代码分析等,可以自由想像。

这里有一个MethodSwizzlingDemo,通过Method Swizzling,描绘每一个View的border。效果图: