一般來說,MVP架構(gòu)在Andriod中用的比較多,但它也可以在iOS中使用。我在重構(gòu)項目的一個功能時,為了改善以前代碼的層次結(jié)構(gòu),同時也想體驗一下MVP的實踐,所以使用了該模式,同時也積累了一點小技巧。
MVP分層模型以及交互關(guān)系如圖所示:

view和model通過presenter進行交互,切斷直接聯(lián)系。
在使用該架構(gòu)后,雖然分層清晰了,但是它有個缺點,presenter中粘合接口過多。
我們知道,mvp各層的交互都是通過接口來完成的,presenter作為中介者,需要實現(xiàn)view操作model層的接口,model層操作UI的接口。而presenter實現(xiàn)這些接口時,大部分是簡單的調(diào)用model和view的接口,并沒有其他額外操作,這樣會導(dǎo)致presenter中粘合方法過多,并且新增接口,presenter也需要新增實現(xiàn)。所以,當(dāng)功能復(fù)雜時,接口暴增,presenter中也會有越來越多的接口實現(xiàn),同時也不利于維護。
先看個簡單的彈幕例子,介紹下上面所說的問題。
Interface
// presenter提供的給view調(diào)用的接口
@protocol DanmuPresenterInterface <NSObject>
@optional
/// 清除聊天記錄
- (void)cleanChats;
@end
// view提供的給presenter調(diào)用的接口
@protocol DanmuViewInterface <NSObject>
@optional
// reload
- (void)reloadTableView;
@end
// model層調(diào)用presenter,更新ui接口
@protocol DanmuDataOutputInterface <NSObject>
@optional
// reload
- (void)reloadTableView;
@end
// prenseter調(diào)用model層,更新數(shù)據(jù)接口
@protocol DanmuDataInterface <NSObject>
@optional
/// 清除聊天記錄
- (void)cleanChats;
@end
Presenter
@interface DanmuPresenter()
// 數(shù)據(jù)層接口
@property (nonatomic, strong) id<DanmuDataInterface> dataManager;
// ui層接口
@property (nonatomic, weak) id<DanmuViewInterface> danmuViewInterface;
@end
@implementation DanmuPresenter
// presenter提供的給view調(diào)用的接口
#pragma mark - DanmuPresenterInterface
/// 清除聊天記錄
- (void)cleanChats {
[self.dataManager cleanChats];
}
// 實現(xiàn)model層調(diào)用更新ui接口
#pragma mark - DanmuDataOutputInterface
// reload
- (void)reloadTableView {
[self.danmuViewInterface reloadTableView];
}
@end
這個例子中,交互關(guān)系如下:

在view中的調(diào)用如下:
// self.presenterInterface為presenter
[self.presenterInterface cleanChats];
在dataManager中調(diào)用如下:
// self.DanmuDataOutputInterface為presenter
[self.DanmuDataOutputInterface reloadTableView];
從上面可以看出,如果DanmuPresenterInterface、DanmuDataOutputInterface有新增接口,presenter中必須新增相應(yīng)實現(xiàn),比較麻煩。
實際上,在danmuView中調(diào)用cleanChats時,presenter只是起了一層中轉(zhuǎn)的作用,內(nèi)部還是直接調(diào)用的dataManager的接口。對于這種類型的接口來說,會極大的增加presenter的接口實現(xiàn)方法數(shù)。
所以,在重構(gòu)過程中,為了減少粘合接口,考慮直接將消息轉(zhuǎn)發(fā)到對應(yīng)的實例中,不需要寫實現(xiàn)方法。如下所示。
- 如果是
danmuView通過DanmuPresenterInterface接口(最后實際上是調(diào)用DanmuDataInterface操作model數(shù)據(jù)),則直接轉(zhuǎn)發(fā)到dataManager。 - 如果是
dataManager調(diào)用DanmuDataOutputInterface接口來更新UI,則直接轉(zhuǎn)發(fā)到danmuView。
// 由于presenter作為中介者,需要實現(xiàn)view操作model層的接口(具體實現(xiàn)為dataManger),model層操作UI的接口(具體實現(xiàn)為chatView),這樣會導(dǎo)致粘合方法過多,并且新增接口,presenter也需要新增實現(xiàn)。故使用消息轉(zhuǎn)發(fā)來簡化處理。
- (id)forwardingTargetForSelector:(SEL)aSelector {
// 轉(zhuǎn)發(fā)DanmuDataInterface實現(xiàn)到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;
}
}
// 轉(zhuǎn)發(fā)DanmuDataOutputInterface實現(xiàn)到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中的實現(xiàn)均可去除。在dataManager調(diào)用的地方為[self.uiInterface reloadTableView],注意這里不能判斷respondsToSelector,因為presenter并沒有實現(xiàn)這些方法,所以判斷了不會走。
但是,這種做法是有限制的。要求presenter中實現(xiàn)的接口,是沒有做任何額外的邏輯,而是直接調(diào)用model層或者ui層的實現(xiàn)。
比如,下面的實現(xiàn)另外調(diào)用了[self xx],就不適用了。
#pragma mark - DanmuPresenterInterface
/// 清除聊天記錄
- (void)cleanChats {
// do something
[self xx];
[self.dataManager cleanChats];
}
以上,就是mvp實踐過程的小結(jié)。