BridgeQ的个人学习博客

学习、记录、分享

一次关于OC运行时和Method Swizzing的小实践

2015年7月23日

起因

最近练习一个项目,经典的UITabBarController加UINavigationController的组合,茫茫多得页面需要设置一个统一的背景色,起初在每个控制器的viewDidLoad方法中都加上这么一段:

1
self.view.backgroundColor = WXGGlobalBackgroundColor; // 设置全局背景色

可是随着开发的进行,控制器和界面越来越多,每一个控制器都要写这么一句同样的代码让我感觉很烦,于是开始寻找一劳永逸的办法。经旁人指点,这其实跟我们想要黑盒测试一个方法一样,不管控制器的viewDidLoad方法做了什么,最后都给他加上设置背景色的代码就OK了。于是马上想到用OC运行时中的Method Swizzing来搞。

经过

有了解决问题的思路,剩下的事就很简单了。我就直接贴代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// UIViewController+Extension.h
#import <UIKit/UIKit.h>
@interface UIViewController (Extension)
@end
//
// UIViewController+Extension.m
#import "UIViewController+Extension.h"
#import <objc/runtime.h>
@implementation UIViewController (Extension)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzled_viewDidLoad));
        BOOL didAddMethod = class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod([self class], @selector(swizzled_viewDidLoad), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
- (void)swizzled_viewDidLoad {
    [self swizzled_viewDidLoad];
//    self.view.backgroundColor = WXGGlobalBackgroundColor;
//    NSLog(@"%@ loaded", self);
    if (![self isKindOfClass:NSClassFromString(@"UIInputWindowController")]) {
        self.view.backgroundColor = WXGGlobalBackgroundColor;
    }
}
@end

关于Method Swizzing的使用和最佳实践,还是推荐去看Mattt大神的文章吧,链接在这里,英文不好的同学可以看南峰子前辈翻译好的,文章链接在这里

Method Swizzing的用法并不难,我就不过多解释了。说一下碰到的问题:第一次写完测试运行的时候,发现模拟器整个界面只有纯色一片,看不到任何控件,进调试工具一看,所有控件都在也都正常,而且在模拟器上点击对应控件的位置,仍然能够触发事件,整个界面就好像被人为涂了一层颜色一样。一时不知该怎么解决,只好NSLog一下,看看都有哪些控制器触发了viewDidLoad方法,然后就真的发现了一个奇怪的家伙:

1
2
// 省略上面的输出结果
<UIInputWindowController: 0x7f8a54060400> loaded

而且这个家伙还是在最后一个,顿时对它怀疑大增,弄不好就是这家伙弄了个全屏的view给我涂在了屏幕上面。接下来就测试一下是不是它搞的鬼,代码同上面的最终代码,最后测试运行发现排除掉它之后界面立马正常了,真的就是它搞的鬼。

结果

最后只能去查这个UIInputWindowController到底是个什么东西,可惜没有查到直接的结果,只知道它是一个苹果的私有API,而且网上已经有人用运行时技术列出了它所包含的成员变量和方法。至于它到底是做什么用的,我们可以从它的命名大致猜一下,多少应该是跟键盘输入有关。我们都知道弹出的键盘也是一个UIWindow对象,不过有别于我们经常使用的keyWindow,那么这些window对象谁来管理呢?可能这是一个思路。

最近时间有限,要继续练习项目了,这个问题就暂时解决到这里,就算是一次对OC运行时和Method Swizzing的小实践吧。