关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(一)


关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(一)

来自百度:

AOP(Aspect Oriented Programming)面向切面编程,

可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。(这里留下疑问,能不能用来实现热更新)。

另外不能说与之相对,只能说是目前流行的编程思想:

来自百度:

OOP(Object Oriented Programming)面向对象编程,

针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

我们可以清楚的认识到,OOP可以对相关业务实体进行封装,并且实现其相关属性和行为,这是很适合去组件化的。而AOP针对的是业务处理过程中的某一阶段,比如说日志记录、异常处理等。
那么我们怎么去使用AOP思想来实现组件化呢?(写下时我也只是在探索阶段,成功与否并不清楚)。

应用一:

在iOS中我们通常使用Method Swizzling来实现AOP,比如说检测OC数据的指针越界问题:
注意:Class获取的并不是NSMutableDictionary而是NSDictionaryM,同理NSMutableArray获取的Class是NSArrayM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#import "NSMutableDictionary+SafeDictionary.h"
#import <objc/runtime.h>
@implementation NSMutableDictionary (SafeDictionary)
+ (void)load{
//避免程序员手贱调用load方法
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[self class] swizzleMethod:@selector(setObject:forKey:) withMethod:@selector(SafeSetObject:forKey:)];
NSLog(@"%@ %@", @"SafeDictionary", [self class]);
});
}
+ (void)swizzleMethod:(SEL)origSelector
withMethod:(SEL)newSelector {
//注意此处获取的并不是NSMutableDictionary而是__NSDictionaryM
//同理NSMutableArray获取的Class是__NSArrayM
Class class = NSClassFromString(@"__NSDictionaryM");

Method originalMethod = class_getInstanceMethod(class, origSelector);
Method swizzledMethod = class_getInstanceMethod(class, newSelector);

BOOL didAddMethod = class_addMethod(class,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)SafeSetObject:(id)anObject forKey:(id)key
{
if (anObject) {
NSLog(@"safe");
[self SafeSetObject:anObject forKey:key];
}else{
NSLog(@"SafeSetObject:forKey: anObject is nil");
}
}
@end

应用二:

基于Method Swizzling实现的打点功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "UIViewController+Analytics.h"
#import <objc/runtime.h>
@implementation UIViewController (Analytics)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(new_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

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

应用三:

Aspects一个基于Objective-C的AOP开发框架,封装了Runtime,我们可以使用Aspects来实现Method Swizzling交换方法。
Aspects中提供了两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface NSObject (Aspects)

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

@end

首先我们在Viewcontroller中添加一个按钮,并实现其点击方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UIButton *btn;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addSubview:self.btn];
[self.btn addTarget:self action:@selector(btnClick) forControlEvents:(UIControlEventTouchUpInside)];
}

- (void)btnClick{
NSLog(@"正常点击");
}



- (UIButton *)btn{
if (!_btn) {
_btn = [UIButton buttonWithType:UIButtonTypeCustom];
_btn.frame = CGRectMake(100, 100, 100, 100);
[_btn setTitle:@"点击" forState:(UIControlStateNormal)];
[_btn setBackgroundColor:[UIColor blueColor]];
}
return _btn;
}
@end

接着我们利用Aspects中提供的+ (id)aspect_hookSelector(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;去实现方法交换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#import "AppDelegate+ChangeEvent.h"
#import "Aspects.h"

typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);
@implementation AppDelegate (ChangeEvent)

- (void)setEvent{
NSDictionary *configs = @{
@"ViewController": @{
@"HookEvents": @[
@{
@"EventName": @"btn",
@"EventSelectorName": @"btnClick",
@"EventHandlerBlock": ^(id<AspectInfo> aspectInfo) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Waring"
message:@"我是交换方法的点击"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil, nil];
[alert show];
});


},
},
],
},
};

for (NSString * className in configs) {
Class class = NSClassFromString(className);
NSDictionary *config = configs[className];
if (config[@"HookEvents"]) {
for (NSDictionary *event in config[@"HookEvents"]) {
SEL selekor = NSSelectorFromString(event[@"EventSelectorName"]);
AspectHandlerBlock block = event[@"EventHandlerBlock"];

[class aspect_hookSelector:selekor
withOptions:AspectPositionInstead
usingBlock:^(id<AspectInfo> aspectInfo) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(aspectInfo);
});
} error:NULL];

}
}
}

}



@end

此时,我们再去点击btn,得到的响应事件即为我们重写过的事件。

本文Demo地址:https://github.com/Flonger/AOPandPod

感谢以下:
https://www.jianshu.com/p/f58a9e4be184


文章作者: Flonger
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Flonger !
 上一篇
关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(二) 关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(二)
关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(二)既然上一篇讲到了利用AOP思想可以动态的添加功能,那我们这篇就讨论下可否实现理论上的热更新。 我们同样利用Aspects去实现 首先我们可以查看Aspects源
2018-02-02
下一篇 
Android开发遇到的问题 Android开发遇到的问题
Android开发遇到的问题1. NDK环境配置本地NDK版本过高,可去下载r16版本,然后配置SDK Location ->NDK Location。 2.模拟器联网配置如需链接Google服务,安装shadowsocks。
2017-08-14
  目录