【学习笔记】使用 Aspects 实现 AOP 以及一点点探索

557 阅读8分钟

导入Aspects

pod 'Aspects'

摆代码

定义 TestAOPModel 类,.m代码如下:


#import "TestAOPModel.h"

@implementation TestAOPModel

+ (NSString *)testClassMethod:(NSString *)string {
   NSLog(@"我是类方法");
   NSLog(@"这是类方法接收到的参数:---【%@】", string);
   return @"我是类方法的返回值";
}

- (NSString *)testInstanceMethod:(NSString *)string {
   NSLog(@"我是对象方法");
   NSLog(@"我是对象方法接收到的参数:---【%@】", string);
   return @"我是对象方法的返回值";
}
@end

再为该类定义 Hook 分类,.m代码如下:


#import "TestAOPModel+Hook.h"
#import <objc/runtime.h>
#import <Aspects.h>
// 此分类文件不用包含原因
// 1、load 方法会在 app main 函数调用之前调用,
// 2、而且每个类的所有 load 方法都会调用,即使该类有很多 category ,每个 category 的方法都会被调用,其中类本身的 load 方法最先调用。
// 3、父类和子类的 load 方法都会调用,父类的 load 方法要先于子类调用。

@implementation TestAOPModel (Hook)

+ (void)load{
   
//    对于整个类所有的对象进行操作  【此处的代码定义为 区块1】
   [self aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionBefore)
                  usingBlock:^(id info){
       NSLog(@"hook-对象方法调用之前");
   } error:NULL];
   [self aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionInstead)
                  usingBlock:^(id info){
       NSLog(@"hook-对象方法被替换");
   } error:NULL];
   [self aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionAfter)
                  usingBlock:^(id info){
       NSLog(@"hook-对象方法t调用之后");
   } error:NULL];
   
//    对类方法进行替换  【此处的代码定义为 区块2】
//    这里有一个概念,其实类对象就相当于是元类的实例对象,
//    即: MetaClass --实例化--> Class --实例化--> Instance
   
   Class catMetal = objc_getMetaClass(NSStringFromClass(self).UTF8String);
   [catMetal aspect_hookSelector:@selector(testClassMethod:)
                     withOptions:(AspectPositionBefore)
                      usingBlock:^(id info){
       NSLog(@"hook-类方法调用之前");
   } error:NULL];
   [catMetal aspect_hookSelector:@selector(testClassMethod:)
                     withOptions:(AspectPositionInstead)
                      usingBlock:^(id info){
       NSLog(@"hook-类方法被替换");
   } error:NULL];
   [catMetal aspect_hookSelector:@selector(testClassMethod:)
                     withOptions:(AspectPositionAfter)
                      usingBlock:^(id info){
       NSLog(@"hook-类方法调用之后");
   } error:NULL];
   
  
}
@end

然后在 ViewController 中对代码进行调用测试, .m代码如下:


#import "ViewController.h"
#import "TestAOPModel.h"
#import <Aspects.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   
   
   [TestAOPModel testClassMethod:@"类方法调用"];
   
   TestAOPModel *aObj = [[TestAOPModel alloc] init];
   
//    对 aObj 单独操作,替换了它的方法。【此处的代码定义为 区块3】
   [aObj aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionBefore)
                  usingBlock:^(id info){
       NSLog(@"hook-单独对象方法调用之前");
   } error:NULL];
   [aObj aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionInstead)
                  usingBlock:^(id info){
       NSLog(@"hook-单独对象方法被替换");
   } error:NULL];
   [aObj aspect_hookSelector:@selector(testInstanceMethod:)
                 withOptions:(AspectPositionAfter)
                  usingBlock:^(id info){
       NSLog(@"hook-单独对象方法调用之后");
   } error:NULL];
   
   [aObj testInstanceMethod:@"对象方法调用"];
   
   TestAOPModel *bObj = [[TestAOPModel alloc] init];
   [bObj testInstanceMethod:@"对象方法调用"];
}

请留意代码中标记的某些代码区块

【区块1】:目的为实现对TestAOPModel类对象的所有成员方法进行操作。

【区块2】:目的为实现对TestAOPModel类的类方法进行操作。

【区块3】:目的为实现对TestAOPModel类下的单个对象aObj进行操作。

运行结果

1.先来最简单的,对单个对象方法进行操作

保留【区块3】屏蔽代码【区块1】【区块2】

运行结果如下:

2020-03-18 16:04:35.553539+0800 LcTestTD[24125:1135695] 我是类方法
2020-03-18 16:04:35.553705+0800 LcTestTD[24125:1135695] 这是类方法接收到的参数:---【类方法调用】
2020-03-18 16:04:35.555217+0800 LcTestTD[24125:1135695] hook-单独对象方法调用之前
2020-03-18 16:04:35.555384+0800 LcTestTD[24125:1135695] hook-单独对象方法被替换
2020-03-18 16:04:35.555516+0800 LcTestTD[24125:1135695] hook-单独对象方法调用之后
2020-03-18 16:04:35.555663+0800 LcTestTD[24125:1135695] 我是对象方法
2020-03-18 16:04:35.555772+0800 LcTestTD[24125:1135695] 我是对象方法接收到的参数:---【对象方法调用】

2.对整个类的所有对象方法进行操作

保留【区块1】屏蔽代码【区块2】【区块3】

运行结果如下:

2020-03-18 16:10:22.652811+0800 LcTestTD[24190:1138872] 我是类方法
2020-03-18 16:10:22.653022+0800 LcTestTD[24190:1138872] 这是类方法接收到的参数:---【类方法调用】
2020-03-18 16:10:22.653463+0800 LcTestTD[24190:1138872] hook-对象方法调用之前
2020-03-18 16:10:22.653657+0800 LcTestTD[24190:1138872] hook-对象方法被替换
2020-03-18 16:10:22.653773+0800 LcTestTD[24190:1138872] hook-对象方法调用之后
2020-03-18 16:10:22.653917+0800 LcTestTD[24190:1138872] hook-对象方法调用之前
2020-03-18 16:10:22.654014+0800 LcTestTD[24190:1138872] hook-对象方法被替换
2020-03-18 16:10:22.654117+0800 LcTestTD[24190:1138872] hook-对象方法调用之后

3.对类方法进行操作

保留【区块2】屏蔽代码【区块1】【区块3】

运行结果如下:

2020-03-18 16:13:05.286970+0800 LcTestTD[24215:1140449] hook-类方法调用之前
2020-03-18 16:13:05.287242+0800 LcTestTD[24215:1140449] hook-类方法被替换
2020-03-18 16:13:05.287394+0800 LcTestTD[24215:1140449] hook-类方法调用之后
2020-03-18 16:13:05.287598+0800 LcTestTD[24215:1140449] 我是对象方法
2020-03-18 16:13:05.287709+0800 LcTestTD[24215:1140449] 我是对象方法接收到的参数:---【对象方法调用】
2020-03-18 16:13:05.287801+0800 LcTestTD[24215:1140449] 我是对象方法
2020-03-18 16:13:05.287892+0800 LcTestTD[24215:1140449] 我是对象方法接收到的参数:---【对象方法调用】


基本操作算是完了,下面玩一点骚操作,看看它行不行😁

1.在对类整体操作了成员方法的情况下,对单独对象aObj进行个性化操作

保留【区块1】【区块3】,屏蔽代码【区块2】

运行结果如下:

2020-03-18 16:17:08.669492+0800 LcTestTD[24244:1142604] 我是类方法
2020-03-18 16:17:08.670042+0800 LcTestTD[24244:1142604] 这是类方法接收到的参数:---【类方法调用】
2020-03-18 16:17:08.670819+0800 LcTestTD[24244:1142604] hook-对象方法调用之前
2020-03-18 16:17:08.671159+0800 LcTestTD[24244:1142604] hook-单独对象方法调用之前
2020-03-18 16:17:08.671323+0800 LcTestTD[24244:1142604] hook-对象方法被替换
2020-03-18 16:17:08.671552+0800 LcTestTD[24244:1142604] hook-单独对象方法被替换
2020-03-18 16:17:08.671766+0800 LcTestTD[24244:1142604] hook-对象方法调用之后
2020-03-18 16:17:08.671926+0800 LcTestTD[24244:1142604] hook-单独对象方法调用之后
2020-03-18 16:17:08.672110+0800 LcTestTD[24244:1142604] hook-对象方法调用之前
2020-03-18 16:17:08.672238+0800 LcTestTD[24244:1142604] hook-对象方法被替换
2020-03-18 16:17:08.672404+0800 LcTestTD[24244:1142604] hook-对象方法调用之后

可以看出来对于bObj来说打印的是最后三句,没有问题。对于aObj来说,我期待的是他只会执行区块3的个性化操作,但是他是将区块1区块3的操作都执行了。

2.同时替换类的成员方法和类方法

保留【区块1】【区块2】,屏蔽代码【区块3】

上述代码运行直接报错。

2020-03-18 16:00:05.438989+0800 LcTestTD[24080:1132806] +[TestAOPModel testClassMethod:]: unrecognized selector sent to class 0x10bf93338
2020-03-18 16:00:05.450330+0800 LcTestTD[24080:1132806] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[TestAOPModel testClassMethod:]: unrecognized selector sent to class 0x10bf93338'

经过查找资料得知,Aspects目前无法支持对一个类的类方法和成员方法同时进行操作。

结论

Aspects 是一个非常好的库,api 十分简单,可以说只有一个函数,也很好理解。当然还能在回调块里面获取方法的相应信息,这一步很简单便没有在此演示。

对的同一个对象的多次操作都会发挥效果,所以同一个类可以写多个分类对同一个方法进行拦截,分别处理不同的业务,这些方法都会执行,不会被顶替。

Aspects 现在有一个bug,不能同时对成员方法和类方法进行操作,不过这个需求应该也很少遇见,原因和解决方案有待我有时间看一下源码再进行解答咯😊

多方查找,找到了这个大佬的分享 iOS 切面编程 Aspects 图解