通常來講,MVP架構在Andriod中用的比較多,但它也能夠在iOS中使用。我在重構項目的一個功能時,爲了改善之前代碼的層次結構,同時也想體驗一下MVP的實踐,因此使用了該模式,同時也積累了一點小技巧。bash
MVP分層模型以及交互關係如圖所示:架構
view和model經過presenter進行交互,切斷直接聯繫。ui
在使用該架構後,雖然分層清晰了,可是它有個缺點,presenter中粘合接口過多
。atom
咱們知道,mvp各層的交互都是經過接口來完成的,presenter做爲中介者,須要實現view操做model層的接口
,model層操做UI的接口
。而presenter實現這些接口時,大部分是簡單的調用model和view的接口,並無其餘額外操做
,這樣會致使presenter中粘合方法過多,而且新增接口,presenter也須要新增實現。因此,當功能複雜時,接口暴增,presenter中也會有愈來愈多的接口實現,同時也不利於維護。spa
先看個簡單的彈幕例子,介紹下上面所說的問題。code
Interfacecdn
// presenter提供的給view調用的接口
@protocol DanmuPresenterInterface <NSObject>
@optional
/// 清除聊天記錄
- (void)cleanChats;
@end
複製代碼
// view提供的給presenter調用的接口
@protocol DanmuViewInterface <NSObject>
@optional
// reload
- (void)reloadTableView;
@end
複製代碼
// model層調用presenter,更新ui接口
@protocol DanmuDataOutputInterface <NSObject>
@optional
// reload
- (void)reloadTableView;
@end
複製代碼
// prenseter調用model層,更新數據接口
@protocol DanmuDataInterface <NSObject>
@optional
/// 清除聊天記錄
- (void)cleanChats;
@end
複製代碼
Presenterblog
@interface DanmuPresenter()
// 數據層接口
@property (nonatomic, strong) id<DanmuDataInterface> dataManager;
// ui層接口
@property (nonatomic, weak) id<DanmuViewInterface> danmuViewInterface;
@end
@implementation DanmuPresenter
// presenter提供的給view調用的接口
#pragma mark - DanmuPresenterInterface
/// 清除聊天記錄
- (void)cleanChats {
[self.dataManager cleanChats];
}
// 實現model層調用更新ui接口
#pragma mark - DanmuDataOutputInterface
// reload
- (void)reloadTableView {
[self.danmuViewInterface reloadTableView];
}
@end
複製代碼
這個例子中,交互關係以下:接口
在view中的調用以下:ip
// self.presenterInterface爲presenter
[self.presenterInterface cleanChats];
複製代碼
在dataManager中調用以下:
// self.DanmuDataOutputInterface爲presenter
[self.DanmuDataOutputInterface reloadTableView];
複製代碼
從上面能夠看出,若是DanmuPresenterInterface、DanmuDataOutputInterface
有新增接口,presenter
中必須新增相應實現,比較麻煩。
實際上,在danmuView
中調用cleanChats
時,presenter
只是起了一層中轉的做用,內部仍是直接調用的dataManager
的接口。對於這種類型的接口來講,會極大的增長presenter
的接口實現方法數。
因此,在重構過程當中,爲了減小粘合接口,考慮直接將消息轉發到對應的實例
中,不須要寫實現方法。以下所示。
danmuView
經過DanmuPresenterInterface
接口(最後其實是調用DanmuDataInterface操做model數據
),則直接轉發到dataManager
。dataManager
調用DanmuDataOutputInterface
接口來更新UI,則直接轉發到danmuView
。// 因爲presenter做爲中介者,須要實現view操做model層的接口(具體實現爲dataManger),model層操做UI的接口(具體實現爲chatView),這樣會致使粘合方法過多,而且新增接口,presenter也須要新增實現。故使用消息轉發來簡化處理。
- (id)forwardingTargetForSelector:(SEL)aSelector {
// 轉發DanmuDataInterface實現到dataManager
struct objc_method_description omd = protocol_getMethodDescription(@protocol(DanmuDataInterface), aSelector, NO, YES);
if (omd.name != NULL) {
if ([self.dataManager respondsToSelector:aSelector]) {
return self.dataManager;
}
}
// 轉發DanmuDataOutputInterface實現到danmuView
omd = protocol_getMethodDescription(@protocol(DanmuDataOutputInterface), aSelector, NO, YES);
if (omd.name != NULL) {
if ([self.danmuViewInterface respondsToSelector:aSelector]) {
return self.danmuViewInterface;
}
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
這樣,DanmuDataInterface、DanmuDataOutputInterface
中的接口在presenter
中的實現都可去除。在dataManager
調用的地方爲[self.uiInterface reloadTableView]
,注意這裏不能判斷respondsToSelector
,由於presenter
並無實現這些方法,因此判斷了不會走。
可是,這種作法是有限制的。要求presenter中實現的接口,是沒有作任何額外的邏輯,而是直接調用model層或者ui層的實現。
好比,下面的實現另外調用了[self xx]
,就不適用了。
#pragma mark - DanmuPresenterInterface
/// 清除聊天記錄
- (void)cleanChats {
// do something
[self xx];
[self.dataManager cleanChats];
}
複製代碼
以上,就是mvp實踐過程的小結。