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