2017-08-06iOS開發(fā)
卷首
最近新工作中用到的RAC+MVVM的開發(fā)模式,由于之前都是用MVC官觅,從自己的菜雞水平感覺這兩種設(shè)計模式在思想上還是有些微區(qū)別的,然后自己也是看了挺多關(guān)于這兩個模式異同與使用利弊的文章阐污,但是說真的休涤,代碼這個東西光看看不出個花來,還是要寫出來才能體會的更深,所以我不講這兩種模式的來龍去脈功氨,我也講不清 ^_^序苏, 要是看過比較多理論上的東西,再結(jié)合一下代碼理理思路還是極好滴捷凄。
目的介紹
上面已經(jīng)說了忱详,這是一個關(guān)于怎樣用代碼實現(xiàn)mvvm的記錄,本來之前就想寫一個極其簡單的tableview的代碼就行了來著跺涤,但正好在項目里面遇到一個比較弱雞的問題匈睁,當時思路一下卡住了,就是一個基礎(chǔ)知識桶错,但是當時沒有想通航唆,這種問題吧,在網(wǎng)上搜都不知道怎么搜院刁,在開發(fā)的時候還是比較影響開發(fā)效率的糯钙,所以回來也把那一點兒東西加上了,其實并沒有幾行代碼退腥,就是想給自己提個醒任岸。
效果圖如下
代碼介紹
效果圖就是上面的,功能比較簡單阅虫,大牛們肯定看都不帶看的那種演闭,哈哈哈,我就是比價喜歡從這種簡單功能上弄懂一些東西颓帝,簡單的都不會米碰,就更不指望去搞高深的了。話不多說购城,首先看一下主要的代碼結(jié)構(gòu):
這里分了四個文件夾用來存放view/model/controller/viewModel 當然有些有些view也可以放在viewController里面吕座,這個并沒有什么嚴格要求。viewModel主要是用來處理數(shù)據(jù)邏輯瘪板,將model進行處理之后和view/controller進行交互吴趴,我理解的它是一個數(shù)據(jù)加工工廠,這樣做的目的也就是避免在controller里面處理大量與界面業(yè)務(wù)邏輯無關(guān)的工作嘛侮攀,將數(shù)據(jù)處理專門用viewModel進行處理锣枝。。兰英。(具體的還是參考詳細的文章吧撇叁,我大概一說)分別說一下各個模塊中的代碼實現(xiàn)吧,很好理解畦贸。
Controller
控制器中就這幾行代碼陨闹,將自定義的mainView進行frame布局楞捂,初始化viewModel,是不是看著要比以前養(yǎng)眼了趋厉。
- (void)viewDidLoad { ? ?[super viewDidLoad]; ? ?self.title = @"第一頁"; ? ?self.view.
backgroundColor = [UIColor whiteColor]; ? ?self.viewModel =
[LGJMainViewModel new]; ? ?[self configMainView];}#pragma mark - configView- (void)configMainView { ? ?self.mainView
= [[LGJMainView alloc] initWithViewModel:self.viewModel];
self.mainView.frame = CGRectMake(0, 0, self.view.frame.size.width,
self.view.frame.size.height); ? ?[self.view addSubview:self.mainView];}
LGJMainView
就是我們看到的tableview一個自定義的view寨闹,用來“盛放”我們自定義的view,這里貼上來的代碼就是將tableview單獨放在這個view里面進行出來君账,當然這里的這些代理方法如果你想使這個view簡化還是可以將他們封裝出來的繁堡,我之前有寫過一個對tableview代理方法優(yōu)化的記錄iOS實現(xiàn)UITableViewDataSource與Controller的分離可以參考這個進一步優(yōu)化,這里面沒有什么多說的乡数,就有一點和之前不一樣的帖蔓,就是多了幾個self.viewModel的方法,這個我們下面說
#pragma mark - tableView delegate&dataSource- (NSInteger)
numberOfSectionsInTableView:(UITableView *)tableView {
return [self.viewModel getSectionCount];}- (UIView *)tableView:(UITableView *)tableView viewForHeader
InSection:(NSInteger)section { ? ?SectionModel *sectionModel = [self.viewModel getSectionModel
WithSection:section]; ? ?UILabel *headerLabel =
[self configHeader]; ? ?headerLabel.text = [NSString stringWithFormat:
@"我是第%@個section", sectionModel.sectionName];
return headerLabel;}- (CGFloat)tableView:(UITableView *)tableView heightForHeader
InSection:(NSInteger)section {
return 50;}- (NSInteger)tableView:(UITableView *)tableView numberOfRows
InSection:(NSInteger)section {
return [self.viewModel getCellCountWithIndexPath:section];}- (UITableViewCell *)tableView:(UITableView *)tableView cellFor
RowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *idStr = @"LGJCell"; ? ?LGJCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cell) { ? ? ? ?cell = [[LGJCell alloc] initWithStyle:UITableView
CellStyleDefault reuseIdentifier:idStr]; ? ?} ? ?cell.cellModel = [self.viewModel getRowModelWithIndexPath:
indexPath];
return cell;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAt
IndexPath:(NSIndexPath *)indexPath {
return 50;}- (void)tableView:(UITableView *)tableView didSelectRowAt
IndexPath:(NSIndexPath *)indexPath { ? ?[self.viewModel changeCellModelWithIndexPath:indexPath]; ? ?[tableView reloadData];}#pragma mark - coustom tableViewHeader - (UILabel *)configHeader
{ ? ?UILabel *headerLabel = [UILabel new]; ? ?headerLabel.backgroundColor = [UIColor whiteColor]; ? ?headerLabel.font = [UIFont systemFontOfSize:14]; ? ?headerLabel.textColor = [UIColor redColor]; ? ?headerLabel.textAlignment = NSTextAlignmentCenter;
return headerLabel;}
LGJViewModel
看一下.h文件中瞳脓,主要就是一些外部需要調(diào)用的方法塑娇,比如在這里我們使用比較多的就是和tableview代理方法相關(guān)的很多方法比較多,如果是在MVC中劫侧,那么我們這些數(shù)據(jù)操作很有可能會寫在controller里面埋酬,controller里面的內(nèi)容也就不像我們剛看見的那樣簡潔了,還有一個就是我在.h文件中聲明了一個block烧栋,其實用RAC+MVVM開發(fā)的話写妥,RAC框架有很多自身封裝好的的block也就是Signal供我們使用,也就減少了我們比較容易頭疼也比較容易忽略的block的循環(huán)引用和內(nèi)存泄漏审姓,等我再熟悉熟悉RAC再專門去說它吧珍特,這里我們就用block先這樣處理,這個不是不可能的魔吐。(在這里這個block沒有用到扎筒,一開始想用來處理一些東西來著,后來沒有用酬姆,之所以沒有刪嗜桌,是想說一下,在mvvm中如果我們沒有用RAC框架辞色,我們可以用block來進行一些回調(diào)操作)
.h
typedef void(^UpdateCellBlock)(NSIndexPath *indexPath);
@interface LGJMainViewModel : NSObject
@property (nonatomic, copy) UpdateCellBlock updateCellBlock;
- (void)changeCellModelWithIndexPath:(NSIndexPath *)indexPath;
- (RowModel *)getRowModelWithIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)getCellCountWithIndexPath:(NSInteger)section;- (NSInteger)getSectionCount;
- (SectionModel *)getSectionModelWithSection:(NSInteger)section;
在.m文件中我做了一個假數(shù)據(jù)骨宠,用來模擬section和cell中的數(shù)據(jù),這個會有用的相满,就在下面我要說的那個坑层亿。
.m
@interface LGJMainViewModel ()
@property (nonatomic, strong)NSMutableArray *listArr;
//盛放所有model的數(shù)組
@end
@implementation LGJMainViewModel
- (instancetype)init {
if (self = [super init]) { ? ? ? ?[self configModelArr]; ? ?} ? ?return self;}- (void)configModelArr { ? ?self.listArr = [NSMutableArray array];
for (int i = 0; i < 10; i++) { ? ? ? ?SectionModel *model = [SectionModel new]; ? ? ? ?model.sectionName = [NSString stringWithFormat:@"%d", i];
NSMutableArray *mutArr = [NSMutableArray array];
for (int j = 0; j < 20; j++) { ? ? ? ? ? ?RowModel *rowModel = [RowModel new]; ? ? ? ? ? ?rowModel.name = [NSString stringWithFormat:@"第%d行", j]; ? ? ? ? ? ?rowModel.detail = [NSString stringWithFormat:
@"我是第%d行, 多多指教", j]; ? ? ? ? ? ?[mutArr addObject:rowModel]; ? ? ? ?} ? ? ? ?model.rowModelArr = mutArr; ? ? ? ?[self.listArr addObject:model]; ? ?}}
這里就是我們在.h文件中看見的那些方法的實現(xiàn)了立美,在viewModel中對請求的數(shù)據(jù)或者本地的數(shù)據(jù)處理之后匿又,返回給外部使用(這里說的不專業(yè)了,明白這個道理就好&—— &)
#pragma mark - get CellModel- (RowModel *)getRowModelWithIndexPath:
(NSIndexPath *)indexPath { ? ?SectionModel *secModel = [self.listArr objectAtIndex:
indexPath.section]; ? ?NSArray *rowArr = secModel.rowModelArr; ? ?RowModel *rModel = [rowArr objectAtIndex:indexPath.row];
return rModel;}#pragma mark - cell/section Count- (NSInteger)getSectionCount {
return self.listArr.count;}- (NSInteger)getCellCountWithIndexPath:(NSInteger)section { ? ?SectionModel *secModel = [self.listArr objectAtIndex:section];
return secModel.rowModelArr.count;}#pragma mark - get SectionModel- (SectionModel *)getSectionModel
WithSection:(NSInteger)section { ? ?SectionModel *sModel = [self.listArr objectAtIndex:section];
return sModel;}
這個坑就是在這兒悯辙,想實現(xiàn)的效果是琳省,當我點擊cell的時候,我替換這個cell對應(yīng)的model數(shù)據(jù)躲撰,一開始是用的被注釋掉的方法针贬,這個稍微有些經(jīng)驗的都能想到這個不行,可是我就是那個掉坑的拢蛋,本來想著我找到對應(yīng)的section的model桦他,在sectionModel中找到對應(yīng)的RowModel然后將新model替換掉舊的。perfect谆棱。快压。。運行之后發(fā)現(xiàn)是行不通的垃瞧,然后用下面在數(shù)組中遍歷查找的方法進行替換解決的蔫劣。關(guān)于這問題我的理解是, 數(shù)組中存放的是model的指針个从,我用newModel替換oldModel脉幢,替換的只是model的指針,但是數(shù)組中儲存model的指針沒有改變嗦锐,所以數(shù)組并不會改變它保存的對應(yīng)位置的指針嫌松,所以說數(shù)組中對應(yīng)位置儲存的還是oldModel的指針。 這個是我的理解奕污,如果有不對麻煩告知了萎羔。
#pragma mark - change Cell Model- (void)changeCellModelWithIndexPath:
(NSIndexPath *)indexPath {// ? ?RowModel *rm = [RowModel new];
// ? ?rm.name = @"新替換的name";
// ? ?rm.detail = @"新替換的detail";//
// ? ?SectionModel *sModel = [SectionModel new];
// ? ?sModel = [self.listArr objectAtIndex:indexPath.section];
// ? ?NSArray *tempCellArr = sModel.rowModelArr;
// ? ?RowModel *rModel = [tempCellArr objectAtIndex:indexPath.row];
// ? ?// ? ?rModel = rm;// ? ?//
if (self.updateCellBlock) {//
self.updateCellBlock(indexPath);// ? ?} ? ?for (int i = 0; i < self.listArr.count; i++) { ? ? ? ?SectionModel *sModel = [SectionModel new];
if (i == indexPath.section) { ? ? ? ? ? ?sModel = [self.listArr objectAtIndex:i];
for (int j = 0; j < sModel.rowModelArr.count; j++) { ? ? ? ? ? ? ? ?RowModel *rModel = [RowModel new];
if (j == indexPath.row) { ? ? ? ? ? ? ? ? ? ?rModel = [sModel.rowModelArr objectAtIndex:j]; ? ? ? ? ? ? ? ? ? ?rModel.name = @"替換***"; ? ? ? ? ? ? ? ? ? ?rModel.detail = @"我是被替換的新cell"; ? ? ? ? ? ? ? ?} ? ? ? ? ? ?} ? ? ? ?} ? ?}}
關(guān)于model還是我們以前用的
@interface?SectionModel?:?NSObject
@property?(nonatomic,?copy)?NSString?*sectionName;
@property (nonatomic, strong) NSArray *rowModelArr;
@end
總結(jié)
寫這篇主要是記錄一個最基本的mvvm思想的實現(xiàn)還有一個以后絕對不能再犯的錯誤,遇到問題多想想碳默,別浮躁贾陷。晚安,明天還要上班嘱根,還有bug等我拯救昵宇。。儿子。
demo:https://github.com/irembeu/LGJ_MVVM_TestDemo