ios开发Runtime详解part3(Method swizzling)

  在 ios开发 Runtime 详解part1和 ios开发 Runtime 详解part2(动态方法解析)中我大致介绍了runtime的基本功能,在这篇文章里,重点介绍一下runtime的一个重要的功能---method swizzling。
  说到method swizzling,不得不介绍一下AOP(Aspect Oriented Programming),即面向切面编程。 AOP在java开发中因为有着一个牛逼的框架spring的存在使得AOP能够得以发扬光大,那么在ios开发中,AOP有哪些作用呢?下面我来大致列举一下:
1、记录日志,这也是用的最多的一种。
2、事务管理,如数据库的提交。
3、处理缓存。
4、安全检查,如权限管理。
  由于汉字的博大精深,切面两个字已经将这一思想做了很好的诠释,但是如果没有深入的体会还是很难理解的。我们知道,OOP(面向对象)是把一切操作都针对对象进行操作,而面向切面则是对切面进行的操作,也就是对业务的某一个层面进行的操作。
  好比我们要对所有的网络请求做一个日志功能,大家首先想到的办法肯定是在网络请求的代码里面加上日志请求的代码,但是假设这个网络请求的代码是被封装起来的,我们没有办法去改变这个请求的源代码,这时候就可以用method swizzling来用我们自定义的方法来替换原有的网络请求的方法,在里面加上日志请求的代码,同时也能够执行网络请求代码。也就是在既有的业务层面中插入新的切面,来处理通用的功能。

那么,method swizzling怎么写呢?
先上代码:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // 如果要替换class method,用下面的方法:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(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);
        }
        
    });
}

#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

  使用method swizzling是存在隐患的,一旦使用不当会带来很大麻烦, 下面就列举一下隐患以及避免的办法,如果你不是很熟悉method swizzling,不要轻易在项目中使用!

1、 method swizzling是非原子性的,也就是说method swizzling是非线程安全的。

  我们知道,+ load方法是在类被初始化的时候调用,+ initialize是程序第一次调用方法前调用的,如果我们把method swizzling写在+ initialize方法里, 就可能出现原方法可能在方法替换之前执行,所以所有的method swizzling都应该写在+ load方法里。

2、method swizzling可能会改变原来类中的代码

  使用method swizzling是为了改变原有的方法,如果你只为了一个地方替换了方法,可这时所有用到这个方法的地方都受到了改变,带来的危害是无法估量的。所以我们在替换的方法里一定要调用替换的方法(如上面例子中的[self xxx_viewWillAppear:animated]),因为替换的方法已经替换了原有的实现,所以不是递归调用,如果继续调用原生的实现则会出现递归循环。

3、method swizzling可能会造成方法名冲突

  想象一下,如果你在类中用method swizzling中替换了一个方法,又在category中又扩展了这个方法,这时候就会出现方法名冲突,通过给替换的方法设一个指针可以解决这个问题:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}
// 在NSObject的category中可以添加一个通用方法来做安全的method swizzling
@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
4、使用method swizzling时在传递参数时可能改变参数的值,使用上面的方法可以解决。
5、方法替换的顺序问题

  想象一下,我们按UIButton、UIControl、UIView的顺序在它们之中替换了三个setFrame方法,和我们按UIView、UIControl、UIButton这个顺序替换三个setFrame方法的结果是不一样的。那么怎么保证这个顺序问题呢?
  如果我们只是要在载入类中使用,只要在load中进行method swizzling就可以了,因为super class的load总是在子类的load之前执行,从而保证了顺序问题。

6、不容易debug

  写好文档、写好文档、写好文档,重要的事情说三遍,method swizzling一定要写好文档,否则就像埋下了一个一个地雷,后患无穷。
  总结:method swizzling可以方便的增加切面,用很少的代码就可以实现aop,使用的过程中一定要注意可能遇到的问题,使用得当必定是开发中的一把利器。

你可能感兴趣的:(ios开发Runtime详解part3(Method swizzling))