美文网首页
Method Swizzling 与 Aspect Orient

Method Swizzling 与 Aspect Orient

作者: ghost__ | 来源:发表于2018-05-09 15:37 被阅读14次

一些概念:
Method Swizzling:是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

Aspect Oriented Programming(面向切面编程):一种编程方式,在指定的地方添加一些自定义代码。
例如一些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。这种程序设计问题,业界给了一个名字:Cross Cutting Concerns
而用 Method Swizzling 动态给指定的方法添加代码,以解决 Cross Cutting Concerns 的编程方式就叫:Aspect Oriented Programming(面向切面编程)

初识三种method-swizzing的使用

1.method_exchangeImplementations 方法交换 交换SEL->IMP

@interface Change : NSObject
+ (void)eat;
+ (void)run;
@end

@implementation Change
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method eat = class_getClassMethod(self, @selector(eat));
        Method run = class_getClassMethod(self, @selector(run));
        method_exchangeImplementations(eat, run);
    });
}
+ (void)eat {
    NSLog(@"吃了");
}
+ (void)run {
    NSLog(@"正准备跑");
}
@end

2.class_replaceMethod 取代方法实现 IMP

@interface Replace : NSObject
+ (void)sleep;
@end
@implementation Replace
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        class_replaceMethod(self, @selector(sleep), sleepFunction, "");
    });
}
+ (void)sleep {
    NSLog(@"马上睡");
}
void sleepFunction() {
    NSLog(@"还不想睡");
}
@end

3.class_addMethod 添加方法实现 IMP

@interface AddMethod : NSObject
+ (void)work;
@end
@implementation AddMethod
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(work), workFunction, "");
    });
}
void workFunction() {
    NSLog(@"上班");
}
@end

可能出现的错误

1.继承 父类实现了sleep方法 子类没有实现。当进行方法交换的时候 发现将父类的方法实现也交换了

@interface Super : NSObject
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Super
+ (void)run {
    NSLog(@"ErrorSuper_跑了");
}
+ (void)sleep {
    NSLog(@"ErrorSuper_睡了");
}
+ (void)eat {
    NSLog(@"ErrorSuper_吃了");
}
@end
@interface Sub : Super
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Sub
+ (void)load {
    //取代实现
    class_replaceMethod(objc_getMetaClass(object_getClassName(self)), @selector(run), replaceRun, "");
    
    //添加实现
    class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), addEat, "");
    
    //交换实现
    method_exchangeImplementations(class_getClassMethod(self, @selector(sleep)), class_getClassMethod(self, @selector(changeMethod)));
}


//取代的实现
void replaceRun() {
    NSLog(@"ErrorSub_取代了跑的实现,不想跑");
}

//添加实现
void addEat() {
    NSLog(@"ErrorSub_添加了吃的实现,不想吃");
}


//交换的方法
+ (void)changeMethod {
    NSLog(@"ErrorSub_交换的方法");
}
@end

原因:当进行方法交换的时候,Sub并没有实现sleep方法 那么便会到父类那边寻找sleep的实现。再进行交换的时候,自然交换的便是父类的sleep方法。
解决:给Sub添加上sleep的实现,可以使用class_addMethod动态添加 在进行交换之前添加

2.多次执行方法交换 并没有出现预期效果
关于这一点,只能说是粗心的原因。
假设:方法1与2 进行交换,预期是调用1实现2 调用2实现1
实现:交换的函数执行了两次
分析:
第一次交换,1->2 2->1
第二次交换,1->1 2->2 (在上次的交换中,1的实现是2的实现,2 的实现是1的实现)。
就是换来换去,结果就晕了。
所以,很多资料上面都建议。将方法的交换写在load里面。但是也不能确保不会有人在子类中写上一个[super load] (手贱)。那么在load中再加上一个dispatch_once

比较完整的方法交换形态

@interface Complete : NSObject
+ (void)run;
+ (void)eat;
@end
@implementation Complete
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //获取需要交换的方法
        Method run = class_getClassMethod(self, @selector(run));
        Method eat = class_getClassMethod(self, @selector(eat));
        
        //添加eat方法 为什么要添加 是为了防止自身没有 而父类有 进行方法交换的时候 交换了父类的方法实现 显然这不是我们想要的
        //将run的实现添加到 新添加的eat方法上
        BOOL isAdd = class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), method_getImplementation(run), method_getTypeEncoding(run));
        
        //判断
        if (isAdd) {
            //添加成功 表示自身确实没有eat方法
            //那么这个时候只需要将eat的方法实现 替换到 run方法上即可
            //是否还记得 判断之前我们添加过SEL为eat  IMP为run的实现 的eat方法
            class_replaceMethod(self, @selector(run), method_getImplementation(eat), method_getTypeEncoding(eat));
        }else {
            //添加失败  表示自身存在eat方法
            //那么这个时候只需要将 run 与 eat 的IMP(方法实现) 进行交换就好了
            method_exchangeImplementations(run, eat);
        }
    });
}

+ (void)run {
    NSLog(@"跑了");
}
+ (void)eat {
    NSLog(@"吃了");
}
@end

小例子 利用类别判断当前是那个控制器 同时也可以做很多事 例如统计

@implementation UIViewController (swizzing)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
        Method swizzMethod = class_getInstanceMethod(self, @selector(swizz_viewWillAppear:));
        
        BOOL isAdd = class_addMethod(self, @selector(viewWillAppear:), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        
        if (isAdd) {
            class_replaceMethod(self, @selector(swizz_viewWillAppear:), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else {
            method_exchangeImplementations(originalMethod, swizzMethod);
        }
    });
}

- (void)swizz_viewWillAppear:(BOOL)animated {
    //这里调用自身并不会引起递归
    //记得两个方法的实现已经进行交换了么
    //这里的调用 是调用了系统的方法
    [self swizz_viewWillAppear:animated];
    if ([self isKindOfClass:[ViewController class]]) {
        NSLog(@"当前控制器是ViewController");
    }else if ([self isKindOfClass:[PVC1 class]]) {
        NSLog(@"当前控制器是PVC1");
    }else if ([self isKindOfClass:[PVC2 class]]) {
        NSLog(@"当前控制器是PVC2");
    }else if ([self isKindOfClass:[PVC3 class]]) {
        NSLog(@"当前控制器是PVC3");
    }
}
@end

当然了,实现这种事情,有很多种方法。
但是同时也有很多局限性,可以参考知识链接里面的解释;
在类别中 你没法 super .... 例如[super viewWillAppear]

Logging 的代码都很相似,通过继承或类别重写相关方法是可以把它从主要逻辑中剥离出来。但同时也带来新的问题:

1.你需要继承 UIViewController, UITableViewController, UICollectionViewController 所有这些 ViewController ,或者给他们添加类别;

2.每个 ViewController 里的 ButtonClick 方法命名不可能都一样;

3.你不能控制别人如何去实例化你的子类;

4.对于类别,你没办法调用到原来的方法实现。大多时候,我们重写一个方法只是为了添加一些代码,而不是完全取代它。

5.如果有两个类别都实现了相同的方法,运行时没法保证哪一个类别的方法会给调用。

关于AOP,参考知识链接里面的东西。能够了解的更加透彻
知识链接:
AOP
SEL-IMP
Aspects

体验小Demo:demo

分享一个关于main函数调用之前系统所做的一些事情:
https://www.jianshu.com/p/43db6b0aab8e
https://blog.csdn.net/yu_4074/article/details/54966782

相关文章

网友评论

      本文标题:Method Swizzling 与 Aspect Orient

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