美文网首页
MVP+Context模式

MVP+Context模式

作者: 人生若只如初见丶_a4e8 | 来源:发表于2023-04-06 09:52 被阅读0次

    记录下自己项目中使用的MVP+Context模式。

    1、传统的 MVP 和MVP+Context的区别

    MVP.png
    • 传统的 MVP 是 MVC 设计模式派生出来的, Presenter 完全把 Model 和 View 进行了分离,通过Presenter发命令来控制View的交互,主要的程序逻辑在 Presenter 里实现。


      MVP+Context.png
    • MVP+Context则在MVP的基础上添加层路由(context),进一步减少相互依赖,通过context可以取到所需要的对象

    2、Context的实现

    • 首先创建context路由对象,保存着所有的交互对象
    @interface XJContext : NSObject
    
    ///p层 逻辑业务
    @property (nonatomic, strong) XJPresenter *presenter;
    ///view层UI展示
    @property (nonatomic, strong) XJBaseView *view;
    ///UI响应
    @property (nonatomic, strong) XJInteractor *interactor;
    
    @end
    
    • 然后为NSObject添加context属性
    @interface XWeakObjectContainer : NSObject
    
    @property (nonatomic, readonly, weak) id weakObject;
    
    - (instancetype)initWithWeakObject:(id)object;
    @end
    
    @interface NSObject (Context)
    
    @property (nonatomic, strong, nullable) XJContext *context;
    @end
    
    - (instancetype)initWithWeakObject:(id)object {
        self = [super init];
        if (self) {
            _weakObject = object;
        }
        
        return self;
    }
    
    @end
    
    @interface NSObject ()
    /// 弱网时presenter的context被controller释放,造成内测泄漏,访问已释放对象。
    /// 或者控制器销毁的时候将presenter的context置nil。
    //@property (nonatomic ,strong) NSHashTable *family;
    
    @end
    
    @implementation NSObject (Context)
    
    - (void)setContext:(XJContext *)objc {
        // 通过NSHashTable判断
    //    if (!self.family) {
    //        self.family = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
    //        [self.family addObject:objc];
    //    }
        XWeakObjectContainer *container = [[XWeakObjectContainer alloc] initWithWeakObject:objc];
          objc_setAssociatedObject(self, @selector(context), container, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (XJContext *)context {
        // 通过NSHashTable判断
    //    if (self.family.allObjects.count == 0){
    //        return nil;
    //    }
    
        XWeakObjectContainer *container = objc_getAssociatedObject(self, @selector(context));
        id curContext = container.weakObject;
        
        if (curContext == nil && [self isKindOfClass:[UIView class]]) {
            UIView *view = (UIView *)self;
            UIView *supView = view.superview;
            while (supView != nil) {
                if (supView.context != nil) {
                    curContext = supView.context;
                    break;
                }
                supView = supView.superview;
            }
            
            if (curContext != nil) {
                [self setContext:curContext];
            }
        }
        return curContext;
    }
    
    • 通过XWeakObjectContainer中间件,来生成weak属性的context对象,以解决在context互相持有的循环引用问题,但是这样出了作用域context就会被释放,为了保证他的生命周期
    @interface UIViewController (MVPConfig)
    
    @property (nonatomic, strong) XJContext *rootContext;
    @property (nonatomic, assign) BOOL mvpEnable;
    
    /// 注册MVP
    - (void)configMVP:(NSString *)name;
    - (void)configMVP;
    
    @end
    
        self.rootContext = [[XJContext alloc] init]; //strong
        self.context = self.rootContext;//weak
    
    • 最后需要规范的命名。在vc初始化的时候创建所需要的类,
     //presenter
        Class presenterClass = NSClassFromString(xj_StringFormat(@"%@Presenter", name));
        if (presenterClass != NULL) {
            self.context.presenter = [presenterClass new];
            self.context.presenter.context = self.context;
        }
        
        //interactor
        Class interactorClass = NSClassFromString(xj_StringFormat(@"%@Interactor", name));
        if (interactorClass != NULL) {
            self.context.interactor = [interactorClass new];
            self.context.interactor.context = self.context;
        }
        
        //view
        Class viewClass = NSClassFromString(xj_StringFormat(@"%@View", name));
        if (viewClass != NULL) {
    //        NSString *str = [[NSBundle mainBundle] pathForResource:StringFormat(@"%@View", name) ofType:@"nib"];
    //        if (!str) {
                self.context.view = [viewClass new];
    //        } else {
    //            self.context.view = (XJBaseView *)NSBundleloadNibNamed(NSStringFormat(@"%@View", name));
    //        }
            self.context.view.context = self.context;
        } else {
            XJBaseView *view = XJBaseView.new;
            self.context.view = view;
            self.context.view.context = self.context;
        }
    
    • 然后通过context进行缓存,在context内部达到互相持有的目的
        self.context.presenter.view = self.context.view;
        self.context.presenter.baseController = self;
    
    //    self.context.interactor.view = self.context.view;
    //    self.context.interactor.baseController = self;
    
        self.context.view.presenter = self.context.presenter;
    
    • 子视图的context,通过遍历查找父视图,直到发现父视图有context为止。
       if (curContext == nil && [self isKindOfClass:[UIView class]]) {
            UIView *view = (UIView *)self;
            UIView *supView = view.superview;
            while (supView != nil) {
                if (supView.context != nil) {
                    curContext = supView.context;
                    break;
                }
                supView = supView.superview;
            }
            
            if (curContext != nil) {
                [self setContext:curContext];
            }
        }
    

    3、使用方法

    • 通过在VC添加configMVP完成MVP的注册
    - (void)viewDidLoad {
        [self configMVP:@"YCYHHomeGas"];
        [super viewDidLoad];
    
    }
    
    • 然后根据XJPresenterDelegate提供的协议来展开数据和界面的交互。
    @protocol XJPresenterDelegate <NSObject>
    
    @required
    
    @optional
    
    /// 创建视图
    - (void)buildView;
    /// 创建视图
    - (void)buildView:(nullable XJBaseAdapter *)adapter;
    /// 加载数据
    - (void)loadData:(nullable XJBaseAdapter *)adapter;
    /// 加载更多数据
    - (void)loadMoreData;
    /// 加载更多数据
    - (void)loadMoreData:(nullable XJBaseAdapter *)adapter;
    /// 刷新UI
    - (void)reloadUI;
    /// 刷新UI
    - (void)reloadUIWithData:(nullable id)data;
    /// 返回
    - (void)gotoBack;
    /// 跳转
    - (void)gotoDetail:(nullable id)data;
    
    @end
    
    
    @interface XJPresenter : NSObject <XJPresenterDelegate>
    
    @property (nonatomic, weak) XJBaseView *view;
    @property (nonatomic, weak) UIViewController *baseController;
    
    @end
    

    继承的Presenter通过添加更多的协议来满足项目需求,协议过多时还可通过Interactor,来进行更进一步的职责划分

    按照:VC需要界面展示-> XJ(self.context.view, XJPresenterDelegate, buildView);->界面需要数据内容-> XJ(self.context.presenter, XJPresenterDelegate, loadData:self.adapter);-> 最后刷新界面赋值-> XJ(self.context.view, XJPresenterDelegate, reloadUI); 这样的方式展开交互。

    其中adapter作为通信的数据模型类,保存着所有的模型数据

    参考文章和代码:
    ios架构--MVP

    相关文章

      网友评论

          本文标题:MVP+Context模式

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