1. 程式人生 > >iOS中MVP架構實踐小技巧

iOS中MVP架構實踐小技巧

一般來說,MVP架構在Andriod中用的比較多,但它也可以在iOS中使用。我在重構專案的一個功能時,為了改善以前程式碼的層次結構,同時也想體驗一下MVP的實踐,所以使用了該模式,同時也積累了一點小技巧。

MVP分層模型以及互動關係如圖所示:

QQ20181113-1.png

view和model通過presenter進行互動,切斷直接聯絡。

在使用該架構後,雖然分層清晰了,但是它有個缺點,presenter中粘合介面過多

我們知道,mvp各層的互動都是通過介面來完成的,presenter作為中介者,需要實現view操作model層的介面model層操作UI的介面。而presenter實現這些介面時,大部分是簡單的呼叫model和view的介面,並沒有其他額外操作

,這樣會導致presenter中粘合方法過多,並且新增介面,presenter也需要新增實現。所以,當功能複雜時,介面暴增,presenter中也會有越來越多的介面實現,同時也不利於維護。

先看個簡單的彈幕例子,介紹下上面所說的問題。

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 複製程式碼

這個例子中,互動關係如下:

QQ20181113-2.png

在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實踐過程的小結。