ObjC Runtime 黑魔法 — Method Swizzling

老子叫甜甜 提交于 2019-12-22 18:45:06

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

适用情境

    项目中大量控制器需要在载入时进行日志统计或进行类似的处理。如果直接往所有控制器中进行代码编写,会产生大量的重复的代码,降低了代码后期的可读性,不利于维护。由于所有部分的逻辑代码相同,针对这种情况,以切面编程(AOP)思想为导向,利用 Method Swizzling 能极大降低这种(日志统计)非主要逻辑代码与控制器的耦合度。

  

AOP概念详解(摘自百度百科)

  在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通过Demo来学习(MethodSwizzling_Demo)

1. 创建一个空工程,再为工程中的 ViewController类创建一个分类(Logging)。

2. 在ViewController 中重载 viewDidAppear: 方法。

- (void)viewDidAppear:(BOOL)animated {
    
    [super viewDidAppear:animated];
}

3. 如果每当该控制器被用户选中后,我们都想在控制台通过输出消息观测到,那么应该向 viewDidAppear: 中添加

NSlog(@"%@", NSStringFromClass([self class]));


   以输出设备当前显示的视图所属的控制器。

4. 如果现在需要进行步骤3修改的控制器数量庞大,显然,我们需要另想办法。

5. 在 UIViewController+Logging.m 中导入头文件 objc/runtime.h ,现在我们就要利用 Method Swizzling 来处理这个问题。

6. 在分类的实现文件中添加方法

- (void)swizzled_viewDidAppear:(BOOL)animated {
    
    [self swizzled_viewDidAppear:animated];
    NSLog(@"%@", NSStringFromClass([self class]));
}


   该方法把我们需要添加到所有控制器的逻辑代码集结到了一处。

7. 在分类中继续实现以下方法

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
   /* 判断 class 中是否存在以 originalSelector 为方法名的 方法。
    * 若没有,则添加 以 originalSelector 为方法名,
    * 以 swizzledMethod 为实现的方法!
    * 若有,则不添加。
    */
    /* 不直接进行 method_exchangeImplementations 替换的原因: 如果这个类没有实现 originalSelector,但是其父     * 类实现了, 那么 class_getInstanceMethod 会返回父类的方法。这样,method_exchangeImplementations 替换      * 的是父类的那个方法
     */
    BOOL didAddMethod = class_addMethod( class, originalSelector, 
    method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector, method_getImplementatino(originalMethod), method_g    etTypeEncoding(originalMethod));
    } 
    else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

8. 为了使用分类中我们写好的 swizzled_viewDidAppear: 来替换控制器中的 viewDidAppear: 方法。使用一个比较特殊的方法 +(void)load 。

一般情况下,类别里的方法会重写掉主类里相同命名的方法。如果有两个类别实现了相同命名的方法,只有一个方法会被调用。

但 +load: 是个特例,当一个类被读到内存的时候, runtime 会给这个类及它的每一个分类都发送一个 +load: 消息。

9. 在分类实现中加入

+ (void)load {
    
    swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}

   swizzleMethod 对方法名为 viewDidAppear: 和 方法名为 swizzled_viewDidAppear: 两个方法的实现部分进行互换。

   注意: @selector(viewDidAppear:)中的 viewDidAppear: 方法是 UIViewController 类中的,不是其子类 ViewController 中的。

10. 调用过程见下图

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!