美文网首页Objective-Cios开源iOS开发技巧
一次关于OC运行时和Method Swizzing的小实践

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

作者: 我是乔忘记疯狂 | 来源:发表于2015-07-23 01:52 被阅读2199次

起因

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

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

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

经过

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

// 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方法,然后就真的发现了一个奇怪的家伙:

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

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

结果

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

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

相关文章

网友评论

  • 2f1fc9f1258e:请问一下,有个问题,就是在alertView和actionSheet弹出来的时候,即使判断了UIInputWindowController,还是会出问题,是还要判断actionSheet和alertView这两个吗?
    我是乔忘记疯狂:@风吹来pp凉 我不是什么大神,我当初也是自己试出来的。你试的这些我都没试过,帮不到你了:sweat:
    2f1fc9f1258e:@我是乔忘记疯狂 iOS8的时候判断"_UIAlertShimPresentingViewController"这个,iOS9判断"UIApplicationRotationFollowingController"就不会让alertview和actionsheet弹出的时候用影响view的背景色.如果判断UIInputWindowController会导键盘变颜色呃.另外在iOS9弹出第三方键盘的时候会崩,我加了个if (self.isViewLoaded)就不崩了.... 这些都是我试出来的,因为什么还不知道,希望大神能耐心解答一下,谢谢了~
    我是乔忘记疯狂:@风吹来pp凉 最好用调试工具看一下view的层次结构,或者像文中一样打印输出看看
  • JoeyBlue:这类问题,从长远来看,提取出一个共同的基类更合适,一般所有的常用的类都提前提取出一个自己的基类,比如view controller ,model 等等,swizzling 虽好,可不要贪杯哦
  • ddaa8dae50b0:The load class method is executed serially at the start of your application. You won't have any issues with concurrency if you do your swizzling here.
    method swizzling放在load里面不会遇到并行的问题, load方法只会在程序启动的时候按序被调用一次, 所以dispatch_once这个是多余的.
    大慈大悲大熊猫:@OrlAnd0 有子类的话会被多次调用吧
    ddaa8dae50b0:@大慈大悲大熊猫 我看了, 你去看load的机制就知道了, 在load里调用本身就能保证原子性
    大慈大悲大熊猫:@OrlAnd0 Swizzling should always be done in a dispatch_once.

    Again, because swizzling changes global state, we need to take every precaution available to us in the runtime. Atomicity is one such precaution, as is a guarantee that code will be executed exactly once, even across different threads. Grand Central Dispatch's dispatch_once provides both of these desirable behaviors, and should be considered as much a standard practice for swizzling as they are for initializing singletons. 参考自Mattt得博客。
  • 大慈大悲大熊猫:一般是所有的控制器都抽出基类,方便自定制。
  • sunljz:思路很好,不过多人协作的项目不太敢用这种,就怕到处都写,调试难度增加
    我是乔忘记疯狂:@sunljz 确实有隐患,黑魔法不可多用! :smile:
  • CoderAO:@我是乔忘记疯 哦哦,对,我忽略了这种情况。我很少用tableViewController,怕需求经常会变,tableview都是手动添加上去的,比较可控
  • 我是乔忘记疯狂:@CoderAO 如果既有UIViewController又有UITableViewController呢?
  • CoderAO:值得学习。不过所有控制器的view统一设置背景色最简单的方法是给所有控制器抽一个公共父类作为控制器基类,在控制器基类的viewDidLoad方法中设置背景颜色.
    ddaa8dae50b0:@CoderAO 我觉得楼主的做法很好. 所有视图控制器抽出基类, 只要这个基类一存在, 项目里的人不管出于什么目的, 都难以避免地会使里面的代码随着时间越变越多, 逻辑越来越复杂, 最后导致维护成本越来越高, 这还涉及到管理和架构的问题.

本文标题:一次关于OC运行时和Method Swizzing的小实践

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