MVP+Context模式

记录下自己项目中使用的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容