美文网首页框架、设计模式
iOS面向切面的TableView-AOPTableView

iOS面向切面的TableView-AOPTableView

作者: 学习无底 | 来源:发表于2019-07-21 10:01 被阅读0次

    MeetYouDevs/IMYAOPTableView

    主要是针对在数据流中接入广告的应用场景。

    原理:其实和我们平常的做法一致,就是把广告和数据流整合在一起,返回总数,然后根据不同的位置调用不同的方法。不过这个框架把这种思路,用切面的思路实现了,在广告和数据流层下添加了一个处理层,将判断的代码抽取,通过row与section的对比,不需要在原代码里大量的if / else判断。

    1.IMYAOPTableViewRawModel

    继承自IMYAOPBaseRawModel ,数据流数据model,主要用在以下协议,功能扩展中

    ///需要 tableView.dataSource 实现 tableView:modelForRowAtIndexPath: 协议中的方法
    - (NSArray<IMYAOPTableViewRawModel *> *)allModels;
    
    ///需要 tableView.dataSource 实现 IMYAOPTableViewGetModelProtocol 协议中的方法
    - (nullable id)modelForRowAtIndexPath:(NSIndexPath *)indexPath;
    

    2.IMYAOPTableViewInsertBody

    继承自 IMYAOPBaseInsertBody,管理插入的广告的model,插入的数据要这样包装下

    3.IMYAOPCallProxy

    代理类,这个类会将所有的方法直接调用父类的方法

    - (void)forwardInvocation:(NSInvocation *)invocation {
        id target = self.target;
        if (!target) {
            ///设置返回值为nil
            [invocation setReturnValue:&target];
            return;
        }
    
        Class invokeClass = self.invokeClass;
        NSString *selectorName = NSStringFromSelector(invocation.selector);
        NSString *superSelectorName = [NSString stringWithFormat:@"IMYSuper_%@_%@", NSStringFromClass(invokeClass), selectorName];
        SEL superSelector = NSSelectorFromString(superSelectorName);
        //通过这个,为父类动态添加方法实现来实现,这个地方没法用消息发送到super
        if ([invokeClass instancesRespondToSelector:superSelector] == NO) {
            Method superMethod = class_getInstanceMethod(invokeClass, invocation.selector);
            if (superMethod == NULL) {
                IMYLog(@"class:%@ undefine funcation: %@ ", NSStringFromClass(invokeClass), selectorName);
                return;
            }
            IMP superIMP = method_getImplementation(superMethod);
            class_addMethod(invokeClass, superSelector, superIMP, method_getTypeEncoding(superMethod));
        }
        invocation.selector = superSelector;
        [invocation invokeWithTarget:target];
    }
    

    4.IMYAOPBaseUtils

    提供了一些IndexPath方法,转化为对应的数据流对应的IndexPath,广告的未转化,

    不支持常见的初始化方法

    #pragma mark - 注入 aop class
    
    - (void)injectFeedsView:(UIView *)feedsView {
        struct objc_super objcSuper = {.super_class = [self msgSendSuperClass], .receiver = feedsView};
        ((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDelegate:), self);
        ((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDataSource:), self);
    
        self.origViewClass = [feedsView class];
        Class aopClass = [self makeSubclassWithClass:self.origViewClass];
        if (![self.origViewClass isSubclassOfClass:aopClass]) {
            [self bindingFeedsView:feedsView aopClass:aopClass];
        }
    }
    
    - (void)bindingFeedsView:(UIView *)feedsView aopClass:(Class)aopClass {
        id observationInfo = [feedsView observationInfo];
        NSArray *observanceArray = [observationInfo valueForKey:@"_observances"];
        ///移除旧的KVO
        for (id observance in observanceArray) {
            NSString *keyPath = [observance valueForKeyPath:@"_property._keyPath"];
            id observer = [observance valueForKey:@"_observer"];
            if (keyPath && observer) {
                [feedsView removeObserver:observer forKeyPath:keyPath];
            }
        }
        object_setClass(feedsView, aopClass);
        ///添加新的KVO
        for (id observance in observanceArray) {
            NSString *keyPath = [observance valueForKeyPath:@"_property._keyPath"];
            id observer = [observance valueForKey:@"_observer"];
            if (observer && keyPath) {
                void *context = NULL;
                NSUInteger options = 0;
                @try {
                    Ivar _civar = class_getInstanceVariable([observance class], "_context");
                    if (_civar) {
                        context = ((void *(*)(id, Ivar))(void *)object_getIvar)(observance, _civar);
                    }
                    Ivar _oivar = class_getInstanceVariable([observance class], "_options");
                    if (_oivar) {
                        options = ((NSUInteger(*)(id, Ivar))(void *)object_getIvar)(observance, _oivar);
                    }
                    /// 不知道为什么,iOS11 返回的值 会填充8个字节。。 128
                    if (options >= 128) {
                        options -= 128;
                    }
                } @catch (NSException *exception) {
                    IMYLog(@"%@", exception.debugDescription);
                }
                if (options == 0) {
                    options = (NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew);
                }
                [feedsView addObserver:observer forKeyPath:keyPath options:options context:context];
            }
        }
    }
    
    #pragma mark - install aop method
    - (Class)makeSubclassWithClass:(Class)origClass {
        NSString *className = NSStringFromClass(origClass);
        NSString *aopClassName = [kAOPFeedsViewPrefix stringByAppendingString:className];
        Class aopClass = NSClassFromString(aopClassName);
    
        if (aopClass) {
            return aopClass;
        }
        aopClass = objc_allocateClassPair(origClass, aopClassName.UTF8String, 0);
    
        [self setupAopClass:aopClass];
    
        objc_registerClassPair(aopClass);
        return aopClass;
    }
    

    5.IMYAOPTableViewUtils

    继承自 IMYAOPBaseUtils,要通过TableView 的 aop_utils 方法,获取该实例。可以设置AOP TableView的回调(主要用来做一些数据统计之类的事)

    核心方法,在IMYAOPBaseUtils类中

    1)IMYAOPTableViewUtils (UITableViewDelegate)

    实现UITableViewDelegate协议的所有方法并进行转发

    2)IMYAOPTableViewUtils (UITableViewDataSource)

    实现UITableViewDataSource协议的所有方法并进行转发

    3)IMYAOPTableViewUtils (InsertedProxy)

    获取插入数据的代理方法

    4)IMYAOPTableViewUtils (TableViewProxy)

    对TableView执行原始数据的操作, 不进行AOP的处理,借助IMYAOPCallProxy

    5)IMYAOPTableViewUtils (Models)

    获取数据model,需要datasorce实现特定的方法

    6._IMYAOPTableView

    实现了TableView.h 文件中的所有方法,但方法前加了 aop_

    中间层,转发直接调用TableView.h文件中的方法

    个人理解:是为了与系统方法区别,获取时方便操作

    使用

    1.创建一个类,有个弱属性IMYAOPTableViewUtils

    2.实现数据插入

    需要数据统计的话,要设置广告回调

    插入新数据前,记得清空历史数据

    插入的广告数据,要用IMYAOPTableViewInsertBody包装下,最好是继承它实现各子类,把广告数据模型传进去,不然后续获取数据不方便

    section数据要配合row

    插入完记得刷新

    3.类实现相应协议

    IMYAOPTableViewDelegate(<UITableViewDelegate>), IMYAOPTableViewDataSource, IMYAOPTableViewGetModelProtocol
    

    主要是UITableView 回调方法,比如说cell样式 高度,选中事件等等。

    4.获取要插入广告的UITableView,和广告类关联

    UITableView *feedsTableView;
    self.aopDemo = [IMYAOPTableDemo new];
    self.aopDemo.aopUtils = feedsTableView.aop_utils;
    

    实现

    1.保存业务的Delegate/DataSource

    2.TableView的delegate/dataSource为IMYAOPTableViewUtils

    ​ 这个类的分类里实现了tableView的delegate和datasource里的所有方法,并进行section和row的处理判断,看是数据流的还是广告的,分别调用相应的方法。

    3.动态派生一个TableView的子类

    个人理解:动态派生是为了解决自定义的tableView接入的问题。根据广告数据改变section和row,是为了无感,就是不影响原有的数据流。

    4.设置业务流的tableview的isa指针,指向派生的类

    5.设置动态创建TableView的子类的aop方法

    动态派生的类里面根据runtime API添加UITableView.h文件中方法的实现,实现是在一个继承自tableview的类中写好的。实现中,会根据广告数据改变section和row,然后调用super的实现。

    参考

    collectionview的与tableview的类似。

    相关文章

      网友评论

        本文标题:iOS面向切面的TableView-AOPTableView

        本文链接:https://www.haomeiwen.com/subject/gikmlctx.html