iOS中MVP架構實踐小技巧
一般來說,MVP架構在Andriod中用的比較多,但它也可以在iOS中使用。我在重構專案的一個功能時,為了改善以前程式碼的層次結構,同時也想體驗一下MVP的實踐,所以使用了該模式,同時也積累了一點小技巧。
MVP分層模型以及互動關係如圖所示:
view和model通過presenter進行互動,切斷直接聯絡。
在使用該架構後,雖然分層清晰了,但是它有個缺點,presenter中粘合介面過多
。
我們知道,mvp各層的互動都是通過介面來完成的,presenter作為中介者,需要實現view操作model層的介面
,model層操作UI的介面
。而presenter實現這些介面時,大部分是簡單的呼叫model和view的介面,並沒有其他額外操作
先看個簡單的彈幕例子,介紹下上面所說的問題。
Interface
// 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
複製程式碼
Presenter
@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中的呼叫如下:
// 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實踐過程的小結。