导入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 图解