iOS组件化解耦之AOP面向切面编程(URLRouter模式)

MVVM解耦Demo和博客介绍

面向切面编程(AOP) Demo思路

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

之前写了DJango的框架,他们的模式是MVT,其实和iOS一样,会在方法执行前暴露出函数让我们调用,在执行前加入自己的东西进行干预,DJango里面就是中间件,iOS里面就可以自己写个类,把需要切面的的函数hook出来(swizzle方法),注入完之后再回调

 

面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。
AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

 

 

Demo介绍

1.全局打点上报单独抽离

2.页面首次进入刷新

3.给页面动态添加需要控件

4.维护全局堆栈

这里给的Demo只是一个简单的思路

首先我们的核心思路针对控制器的周期事件进行hook,这里可以提供两种容器,一种是全局静态容器存储全局都公用的功能,还有一种是和ViewController绑定一个Category,每个控制器对应关联一个数组,用来存储需要的切片功能。切片其实就是一个具有某种功能的类,需要的时候注入到容器里面就行了,在hook到生命周期的函数时,拿出来操作即可。

 

1.Hook交换方法

// 交换方法
void MKJControllerLifeCircleSwizzInstance(Class class, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethod =
    class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod)
    {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
    }
    else
    {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
+ (void)load{
    Class viewControllerClass = [UIViewController class];
    MKJControllerLifeCircleSwizzInstance(viewControllerClass,@selector(viewDidAppear:),@selector(mkj_hook_viewDidAppear:));
    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewDidDisappear:), @selector(mkj_hook_viewDidDisappear:));
    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewWillAppear:), @selector(mkj_hook_viewWillAppear:));
    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewWillDisappear:), @selector(mkj_hook_viewWillDisappear:));
}

 

2.全局容器和ViewController的关联容器

// 全局Action存储的懒加载
NSMutableArray * MKJViewControllerGlobalAction() {
    static NSMutableArray *mutableArray = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutableArray = [[NSMutableArray alloc] init];
    });
    return mutableArray;
}

// 全局注册到单利数组中
void MKJViewControllerRegisterGlobalAction(MKJViewLifeCircleBaseAction *action){
    void(^Register)(void) = ^(void) {
        NSMutableArray* actions = MKJViewControllerGlobalAction();
        if (![actions containsObject:action]) {
            [actions addObject:action];
        }
    };
    
    if ([NSThread mainThread]) {
        Register();
    } else
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            Register();
        });
    }
}


// 移除
void MKJViewControllerRemoveGlobalAction(MKJViewLifeCircleBaseAction *action){
    void(^Remove)(void) = ^(void) {
        NSMutableArray* actions = MKJViewControllerGlobalAction();
        if ([actions containsObject:action]) {
            [actions removeObject:action];
        }
    };
    if ([NSThread mainThread]) {
        Remove();
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            Remove();
        });
    }
}

 

/ Category关联属性
- (void)setLifeCircleAction:(NSArray *)array{
    objc_setAssociatedObject(self, MKJViewAppearKey, array, OBJC_ASSOCIATION_RETAIN);
}

- (NSArray *)getLifeCircleAction
{
    NSArray *array = objc_getAssociatedObject(self, MKJViewAppearKey);
    // 如果第一次没有获取到 是 nil if不会成立
    if ([array isKindOfClass:[NSArray class]]) {
        return array;
    }
    return [NSArray array];
}

// 根据页面注册  把需要切入的操作绑定 挂载到对应的VC上面,这个不是全局的,是根据VC绑定的数组的
- (MKJViewLifeCircleBaseAction* )registerLifeCircleAction:(MKJViewLifeCircleBaseAction *)action
{
    NSMutableArray* array = [NSMutableArray arrayWithArray:[self getLifeCircleAction]];
    if ([array containsObject:action]) {
        for (MKJViewLifeCircleBaseAction* act in array) {
            if ([action isEqual:act]) {
                return act;
            }
        }
    }
    [array addObject:action];
    [self setLifeCircleAction:array];
    return action;
}

// 移除
- (void) removeLifeCircleAction:(MKJViewLifeCircleBaseAction *)action
{
    NSArray* array = [self getLifeCircleAction];
    NSInteger index = [array indexOfObject:action];
    if (index == NSNotFound) {
        return;
    }
    NSMutableArray* mArray = [NSMutableArray arrayWithArray:array];
    [mArray removeObjectAtIndex:index];
    [self setLifeCircleAction:array];
}

3.Hook执行方法

// 四个 hook出来的方法
- (void)mkj_hook_viewWillAppear:(BOOL)animated{
    [self mkj_hook_viewWillAppear:animated];
    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {
        if ([action respondsToSelector:@selector(hookViewController:viewWillAppear:)]) {
            [action hookViewController:self viewWillAppear:animated];
        }
    }];
}

- (void)mkj_hook_viewDidAppear:(BOOL)animated{
    [self mkj_hook_viewDidAppear:animated];
    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {
        if ([action respondsToSelector:@selector(hookViewController:viewDidAppear:)]) {
            [action hookViewController:self viewDidAppear:animated];
        }
    }];
}


- (void)mkj_hook_viewWillDisappear:(BOOL)animated{
    [self mkj_hook_viewWillDisappear:animated];
    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {
        if ([action respondsToSelector:@selector(hookViewController:viewWillDisappear:)]) {
            [action hookViewController:self viewWillDisappear:animated];
        }
    }];
}

- (void)mkj_hook_viewDidDisappear:(BOOL)animated{
    [self mkj_hook_viewDidDisappear:animated];
    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {
        if ([action respondsToSelector:@selector(hookViewController:viewDidDisappear:)]) {
            [action hookViewController:self viewDidDisappear:animated];
        }
    }];
}


/**
 Hook的方法执行代码
 会先获取全局容器和关联的容器进行遍历,然后执行存储在里面的切片Action

 @param block 回调
 */
- (void)mkj_hook_performAction:(MKJAOPActionBlock)block{
    // 1.获取全局Action  单利
    MKJCallBackAction([MKJViewControllerGlobalAction() copy], block);
    
    // 2.获取挂载的Action  根据页面存储的数组
    MKJCallBackAction([[self getLifeCircleAction] copy], block);
}


// 执行 Action
void MKJCallBackAction(NSArray* actions, MKJAOPActionBlock block) {
    for (MKJViewLifeCircleBaseAction* action in actions) {
        if (block) {
            block(action);
        }
    }
}

 

4.创建切片Action

根据上面Hook到的函数,实际上是取出之前注入到容器中的Action,然后执行Action的方法,全局容器是无论哪个控制器都有管理,而可选关联的容器是根据页面绑定的,因此触发的时候把容器self传出去即可

/**
 基类 所有切片的基类 通过vc的周期函数进行处理
 */
@interface MKJViewLifeCircleBaseAction : NSObject

@property (nonatomic,strong,readonly) UIViewController *liveViewController;


- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated;

- (void)hookViewController:(UIViewController *)controller viewDidAppear:(BOOL)animated;

- (void)hookViewController:(UIViewController *)controller viewWillDisappear:(BOOL)animated;

- (void)hookViewController:(UIViewController *)controller viewDidDisappear:(BOOL)animation;


@end

 

5.全局切片

全局切片是不需要在外面写任何处理的代码,直接在本类中load加入到数据就可以回调

@implementation MKJViewControllerLogAction

// 全局注册
+ (void)load{
    // 注册到全局静态数组里面,每个页面周期函数触发hook都会进入这里打印log
    MKJViewControllerRegisterGlobalAction([[MKJViewControllerLogAction alloc] init]);
}


// 这个Action可以用来做全局打印,方便使用,在load里面可以拿掉或者注入,完全不会影响业务逻辑以及污染代码
- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated{
    [super hookViewController:controller viewWillAppear:animated];
    NSLog(@"ViewController---->%@-------SEL------>%@",controller,NSStringFromSelector(_cmd));
}

- (void)hookViewController:(UIViewController *)controller viewDidAppear:(BOOL)animated{
    [super hookViewController:controller viewDidAppear:animated];
}

- (void)hookViewController:(UIViewController *)controller viewWillDisappear:(BOOL)animated{
    [super hookViewController:controller viewWillDisappear:animated];
    NSLog(@"ViewController---->%@-------SEL------>%@",controller,NSStringFromSelector(_cmd));
}

- (void)hookViewController:(UIViewController *)controller viewDidDisappear:(BOOL)animation{
    [super hookViewController:controller viewDidDisappear:animation];
}

@end

 

6.专用切片Action(可选)

专用职责根据上面的容器,是根据页面关联的数组,因此需要在页面上添加小部分注册的代码

- (instancetype)initWithActionBlock:(MKJViewControllerFirstAppearBlock)block{
    self = [super init];
    if (self) {
        _isFirstAppear = YES;
        _actionBlock = block;
    }
    return self;
}

- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated{
    [super hookViewController:controller viewWillAppear:animated];
    if (_isFirstAppear) {
        if (_actionBlock) {
            _actionBlock(controller,animated);
        }
        _isFirstAppear = NO;
    }
    
}
MKJVIewControllerFirstEnterAction *firstAction = [[MKJVIewControllerFirstEnterAction alloc] initWithActionBlock:^(ViewController *controller, BOOL animation) {
        [controller pop:animation];
    }];
    [self registerLifeCircleAction:firstAction];

 

 

这里顺着一个博主的思路和代码写了一下分析了一下,基本逻辑就很清晰的

 

 

底层切片逻辑

基于底层切片,控制器跳转用切片实现,用切面维护一个全局堆栈

 

更新:

上述AOP实现是基于上层SEL和IMP的交换,也是最简单的方式

网上有一个轮子是基于Runtime消息转发截获的,把每个消息都动态指向objc_msgForward进入转发流程来实现

Aspects

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值