寫在前頭:這里有個Demo,可以先下載
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
id model = [self modelsAtIndexPath:indexPath];
cell.model = model;
return cell;
}
還記得這段代碼“cell.model = model;”我們曾經都這么寫,很方便很高大。
1、MVC
曾經
在討論蘋果對MVC的看法之前,讓我們先看看傳統的MVC。
在上圖的情況下,View是無狀態的。一旦Model被改變,Controller就會簡單地渲染它。例如:網頁完全加載后,一旦你按下鏈接,就導航到其他地方。
雖然在iOS應用用傳統的MVC架構也可以實現,但這并沒有多大意義,由于架構問題?——三個實體是緊耦合的,每個實體和其他兩個通信。這大大降低了可重用性——這可不是你希望在你的應用程序看到的。“cell.model = model;”這句類似的代碼出了問題,cell中不應該引入model,要實現cell與model的解耦合。出于這個原因,我們甚至不想編寫規范的MVC示例。
傳統的MVC似乎不適用于現代IOS開發。
蘋果的MVC
愿景:
Controller是View和Model之間的中介,這樣他們就解耦了。最小的可重用單元是Controller,這對我們來說是個好消息,因為我們必須有一個來放那些不適合放入Model的復雜業務邏輯的地方。
從理論上講,它看起來很簡單,但你覺得有些地方不對,對吧?你甚至聽到有人說MVC全稱應該改為Massive View Controller(大量的視圖控制器)。此外,為View controller減負也成為iOS開發者面臨的一個重要話題。
對于MVC
MVC的缺點在于并沒有區分業務邏輯和業務展示, 這對單元測試很不友好。而MVP針對以上缺點做了優化, 它將業務邏輯和業務展示也做了一層隔離, 對應的就變成了MVCP. M和V功能不變, 原來的C現在只負責布局(也就可以說VC就是V), 而所有的邏輯全都轉移到了P層.
2、MVP
MVP 實現了Cocoa的MVC的愿景
這看起來不正是蘋果的MVC嗎?是的,它的名字是MVP(Passive View variant,被動視圖變體)。等等...這是不是意味著蘋果的MVC實際上是MVP?不,不是這樣。如果你仔細回憶一下,View是和Controller緊密耦合的,但是MVP的中介Presenter并沒有對ViewController的生命周期做任何改變,因此View可以很容易的被模擬出來。在Presenter中根本沒有和布局有關的代碼,但是它卻負責更新View的數據和狀態。
來舉個栗子---這里有個Demo
以上是這樣一個功能實現:通過點擊
-
或者+
號調節數據,并且改變的數據會被保存,并且當調節后數據大于7會更換整個列表的數據。1、MVC
先來看看MVC的C
@implementation MVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self loadData];
__weak __typeof(self) weakSelf = self;
self.dataSource = [[LMDataSource alloc] initWithIdentifier:reuserId configureBlock:^(MVPTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
cell.delegate = strongSelf;
cell.indexPath = indexPath;
// cell.model = model;
cell.nameLabel.text = model.name;
cell.numLabel.text = model.num;
} selectBlock:^(NSIndexPath *indexPath) {
NSLog(@"點擊了%ld行cell", (long)indexPath.row);
}];
[self.dataSource addDataArray:self.dataArray];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
self.tableView.dataSource = self.dataSource;
}
#pragma mark - lazy
- (NSMutableArray *)dataArray{
if (!_dataArray) {
_dataArray = [NSMutableArray arrayWithCapacity:10];
}
return _dataArray;
}
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.backgroundColor = [UIColor whiteColor];
[_tableView registerClass:[MVPTableViewCell class] forCellReuseIdentifier:reuserId];
}
return _tableView;
}
C中數據加載
#pragma mark 加載數據
- (void)loadData{
NSArray *temArray =
@[
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"adn",@"imageUrl":@"http://Dean ",@"num":@"9"},
@{@"name":@"sd",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"adin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adfci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"Dsdfn",@"imageUrl":@"http://Dean ",@"num":@"9"}];
for (int i = 0; i<temArray.count; i++) {
Model *m = [Model modelWithDictionary:temArray[i]];
[self.dataArray addObject:m];
}
}
關于cell中按鈕的事件代理
#pragma mark - PresentDelegate
- (void)didClickAddBtnWithNum:(NSString *)num indexPath:(NSIndexPath *)indexPath{
for (int i = 0; i<self.dataArray.count; i++) {
// 查數據 ---> 錢
if (i == indexPath.row) {// 商品ID 容錯
Model *m = self.dataArray[indexPath.row];
m.num = num;
break;
}
}
if ([num intValue] > 6) {
NSArray *temArray =
@[
@{@"name":@"CfC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"sadfa",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"sderfx",@"imageUrl":@"http://Cooci",@"num":@"9"}];
[self.dataArray removeAllObjects];
for (int i = 0; i<temArray.count; i++) {
Model *m = [Model modelWithDictionary:temArray[i]];
[self.dataArray addObject:m];
}
MVPTableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell.delegate && [cell.delegate respondsToSelector:@selector(reloadDataForUI)]) {
[cell.delegate reloadDataForUI];
}
}
}
- (void)reloadDataForUI{
[self.dataSource addDataArray:self.dataArray];
[self.tableView reloadData];
}
分析一下:要進化為MVP,數據加載可以提取到Present里,V(cell)中的代理應該提取到Present中
2、MVP
P (Present)
@implementation Present
- (instancetype)init{
if (self = [super init]) {
[self loadData];
}
return self;
}
- (void)loadData{
NSArray *temArray =
@[
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"adn",@"imageUrl":@"http://Dean ",@"num":@"9"},
@{@"name":@"sd",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"adin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adfci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"CadsC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"Jadsfes",@"imageUrl":@"http://James",@"num":@"9"},
@{@"name":@"Gadsfin",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"adci",@"imageUrl":@"http://Cooci",@"num":@"9"},
@{@"name":@"Dsdfn",@"imageUrl":@"http://Dean ",@"num":@"9"}];
for (int i = 0; i<temArray.count; i++) {
Model *m = [Model modelWithDictionary:temArray[i]];
[self.dataArray addObject:m];
}
}
#pragma mark - lazy
- (NSMutableArray *)dataArray{
if (!_dataArray) {
_dataArray = [NSMutableArray arrayWithCapacity:10];
}
return _dataArray;
}
#pragma mark - PresentDelegate
- (void)didClickAddBtnWithNum:(NSString *)num indexPath:(NSIndexPath *)indexPath{
for (int i = 0; i<self.dataArray.count; i++) {
// 查數據 ---> 錢
if (i == indexPath.row) {// 商品ID 容錯
Model *m = self.dataArray[indexPath.row];
m.num = num;
break;
}
}
if ([num intValue] > 6) {
NSArray *temArray =
@[
@{@"name":@"CfC",@"imageUrl":@"http://CC",@"num":@"9"},
@{@"name":@"sadfa",@"imageUrl":@"http://Gavin",@"num":@"9"},
@{@"name":@"sderfx",@"imageUrl":@"http://Cooci",@"num":@"9"}];
[self.dataArray removeAllObjects];
for (int i = 0; i<temArray.count; i++) {
Model *m = [Model modelWithDictionary:temArray[i]];
[self.dataArray addObject:m];
}
if (self.delegate && [self.delegate respondsToSelector:@selector(reloadDataForUI)]) {
[self.delegate reloadDataForUI];
}
}
}
縮減后的V(VC)怎么樣呢
@implementation MVPViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.pt = [[Present alloc] init];
__weak typeof(self) weakSelf = self;
self.dataSource = [[LMDataSource alloc] initWithIdentifier:reuserId configureBlock:^(MVPTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
// 函數式編程
// RAC 編程思想之集大成者
cell.nameLabel.text = model.name;
cell.numLabel.text = model.num;
cell.indexPath = indexPath;
cell.delegate = weakSelf.pt;
} selectBlock:^(NSIndexPath *indexPath) {
NSLog(@"點擊了%ld行cell", (long)indexPath.row);
}];
[self.dataSource addDataArray:self.pt.dataArray];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
self.tableView.dataSource = self.dataSource;
self.pt.delegate = self;
}
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.backgroundColor = [UIColor whiteColor];
[_tableView registerClass:[MVPTableViewCell class] forCellReuseIdentifier:reuserId];
}
return _tableView;
}
#pragma mark - PresentDelegate
- (void)reloadDataForUI{
[self.dataSource addDataArray:self.pt.dataArray];
[self.tableView reloadData];
}
代碼瞬間減少了一半,就算以后增加邏輯代碼,也只是會增加Present。如果覺得有用可以下載Demo了解更多。如果對LMDataSource
這個類的功能不太了解,這里是使用了代理模式,可以看看設計模式--代理模式(iOS)
MVP大概就是這個樣子了, 相對于MVC, 它其實只做了一件事情, 即分割業務展示和業務邏輯。 展示和邏輯分開后, 只要我們能保證V在收到P的數據更新通知后能正常刷新頁面, 那么整個業務就沒有問題。因為V收到的通知其實都是來自于P層的數據獲取/更新操作, 所以我們只要保證P層的這些操作都是正常的就可以了。即我們只用測試P層的邏輯, 不必關心V層的情況。
當然這個栗子比較簡單,本文更新界面的方法就一個reloadDataForUI
,如果到時候業務復雜、邏輯復雜,更新界面的方法有多個(彈框、菊花等等的),可以通過多個代理方法實現。這樣當然可以,但有沒有更簡單直接明了的方法呢?當然有,那就是MVVM
產生的原因,詳情可以查看我的iOS 從MVP到MVVM。
寫在最后:
希望這篇文章對您有幫助。當然如果您發現有可以優化的地方,希望您能慷慨的提出來。最后祝您工作愉快!