Method swizzling实践

作者: 千煌89 | 来源:发表于2015-05-13 16:08 被阅读2640次

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

    @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
    

    原本的样子是这样的:


    Screen Shot 2015-05-12 at 16.52.05.png

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

    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来看看。

    Paste_Image.png Paste_Image.png

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

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

    Paste_Image.png

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

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

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

    迫不及待的来看看效果:

    Paste_Image.png

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

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

    相关文章

      网友评论

      • 72ea0031e578:除UIInputWindowController之外,还需要添加UIApplicationRotationFollowingController的判断,否则当长按输入框(应弹出“粘贴”框)的时候,会出现同样的现象。
        高浩浩浩浩浩浩:@天下无贼 本身就是UIViewController的类别,这个判断无意义吧
        天下无贼:@千煌89 直接判断是不是继承于UIViewController或者UITableViewController 是不是可以解决??
        千煌89:@arthurdai 并不止这些,后来发现actionsheet等好多都会被覆盖,所以设置颜色这种事情就不用这个了……
      • 南栀倾寒:@千煌89 你只要xcode无法提示都是私有的
      • iHTCboy:@千煌89 好的谢谢!🎓
      • 千煌89:@南栀倾寒 我看UIKit属于public的啊,应该是使用了private的才会被拒绝吧
      • 千煌89:@HTC 你可以看下我文中给出的casa的文章,里面有详说
      • 南栀倾寒:NSClassFromString 创建私有的类 上架app会被apple 拒绝
      • iHTCboy:谢谢分享,很棒!

        我水平有限,不太明白,望楼主指点
        对项目侵略性很大?
        没明白


        不管什么都要继承,而一些TableViewController之类的还没有办法继承。
        一个项目,风格形式继承不→_→是很好吗,不用TVC,用TableView也行啊?

      本文标题:Method swizzling实践

      本文链接:https://www.haomeiwen.com/subject/mhofqttx.html