Method swizzling实践

在项目中我们经常要设置统一的背景色,在之前我是用继承做的。如下:

@interface ZXBaseViewController : UIViewController
@end

@implementation ZXBaseViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self addBackButton];
}

- (void)addBackButton
{
    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
    item.tintColor = [UIColor whiteColor];
    self.navigationItem.backBarButtonItem = item;
    
    [self.view setBackgroundColor:[UIColor redColor]];
}
@end

后来看了casa大神的iOS应用架构谈 view层的组织和调用方案,觉得这种方法耦合度太高,对项目侵略性很大,不管什么都要继承,而一些TableViewController之类的还没有办法继承,又要重写非常麻烦。

于是我想到了使用黑魔法Method swizzling。
Method swizzling的介绍可以看女神念茜的一篇文章:Objective-C的hook方案(一): Method Swizzling

于是没没几分钟,我就写好了一个例子。

@implementation UIViewController (KGTracking)
- (void)addBackButton
{
    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
    item.tintColor = [UIColor whiteColor];
    self.navigationItem.backBarButtonItem = item;
    
    [self.view setBackgroundColor: [UIColor redColor]];
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        swizzleMethod(class, @selector(viewDidLoad), @selector(kg_viewDidLoad));
    });
}

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

- (void)kg_viewDidLoad
{
    [self kg_viewDidLoad];
    [self addBackButton];
}
@end

原本的样子是这样的:


Method swizzling实践_第1张图片
Screen Shot 2015-05-12 at 16.52.05.png

我把上面的文件引入到pch之后,来看看效果:

Method swizzling实践_第2张图片
Paste_Image.png

纳尼?怎么全红了?
我试着加入了如下一个log,看看是什么原因

- (void)kg_viewDidLoad
{
    [self kg_viewDidLoad];
    [self addBackButton];
    NSLog(@"viewDidLoad: %@",[self class]);
}

打印结果如下:

2015-05-13 14:59:17.286 KuaiGou[13323:189187] viewDidLoad: KGTabBarController
2015-05-13 14:59:17.307 KuaiGou[13323:189187] viewDidLoad: UINavigationController
2015-05-13 14:59:17.333 KuaiGou[13323:189187] viewDidLoad: KGHomeViewController
2015-05-13 14:59:17.338 KuaiGou[13323:189187] viewDidLoad: UIInputWindowController

等等,这个UIInputWindowController是什么鬼?会不会就是他在作怪?是不是加个判断是这个类的就不加背景色就行了呢?

Paste_Image.png

结果狠狠的打了我的脸,这个类在UIKit里找不到。
我请教了虾神,虾神让我用reveal看看是不是这玩意作怪,如果是的话,到github上能够找到这个文件。

好的,我先用reveal来看看。

Method swizzling实践_第3张图片
Paste_Image.png
Method swizzling实践_第4张图片
Paste_Image.png

在分离模式下并没有看到什么异样,合并起来就变成一片红色了,基本可以肯定是这个UITextEffectsWindow的颜色被我染红了。

既然问题已经找到了,那我们上github找这家伙吧,然后把他加到工程里,就能解决问题啦!
过不了多久,就顺利的找到了这个repoiOS-Runtime-Headers,里面有个包含了UIInputWindowController的UIKit的framework。

Method swizzling实践_第5张图片
Paste_Image.png

是不是把这个UIKit加入工程,就能顺利引用了呢?
我尝试了下,果不其然,跟sdk里的UIKit冲突了= =好吧,肯定有解决的办法的,只是我还没想到,这条线索到这里中断了,尝试一天并没有解决。

然后第二天午睡的时候,趴在桌上做着梦,我突然想到了NSClassFromString()这个方法。既然这里是特例,而且我也已经准确的知道了类名,何不用这个方法呢?

- (void)kg_viewDidLoad
{
    [self kg_viewDidLoad];
    if (![self isKindOfClass:NSClassFromString(@"UIInputWindowController")]) {
        [self addBackButton];
    }
}

迫不及待的来看看效果:

Method swizzling实践_第6张图片
Paste_Image.png

奈斯啊!没有白费我做梦也想着他。

总结:
没有金刚钻不要揽瓷器活,黑魔法hack一时爽,出了问题的时候可能也会弄得你痛不欲生,给你带来方便的同时一定要慎用!

你可能感兴趣的:(Method swizzling实践)