关于在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