工作中经常会听到这样的声音,“时间赶,先实现了再说,后面找时间再慢慢优化”。扪心自问吧,一个版本开发结束后,除了要改bug,你有多少次回头再去看写过的代码,可能早忘了你要优化什么了。就算你有心要优化,你还要去争取测试资源,如果不是一个部门的就更有难度了。再者,如果实现功能的过程中不注重细节和设计,只图快,那么到了测试阶段发现问题,或者后面的版本需求变动,改起来同样耗时多,还有可能因为缝缝补补让代码变得更加糟糕。唉,小伙伴们如果有似曾相识的经历,欢迎文末留言补充。
什么都放在ViewModel里,反正是MVVM
对于MVVM方式来构建代码,我们通常会封装一个ModuleViewModel
,然后由它拉取数据,保存数据源,生成cell对应的viewModel,这里要说的被膨胀的viewModel就是ModuleViewModel。它要去调用接口、按照业务逻辑组装数据、生成cellViewModel、返回渲染tableView需要的东西,然后它里面可能又被接着放点击事件的跳转、分享逻辑、甚至是埋点相关的......因为它离数据源是最近的,所以把东西放在这里,处理起来是最方便的。但是这个viewModel也就变得越来越臃肿,非常难维护,更不要说可测试性和可复用性了。
给viewController瘦身没错,那也不能把所有东西都转移到ModuleviewModel
中,或其他某个地方。所以要怎么做呢?我建议的做法是分离、封装,把一类事件的处理从viewModel或viewController中分离出去,如果跟某个业务相关的就封装成一个helper,如果是所有业务可以通用的就封装成一个manager。最终达到的目的就是让viewMoodel或viewController成为一个调度中心。
保存数据源的地方,在实现功能的过程中,如果不多加思考,代码量很容易就变得越来越庞大。这样的代码几乎就是一次性的,后面的人在接手时,为了改一个小地方,要把前前后后所有的逻辑都缕清楚,然后改完了还怕牵扯到什么其他的功能。
与具体的DataModel相关的逻辑,都散落在外面
再来说一下用来刷新某一块view的viewModel,比如cellViewModel
。它不是简单的数据结构呀,它里面也可以放方法!它的职责其实是把从服务器拿回来的数据,转换成UI所需要的。所以这个处理过程不要放在生成它的地方,也不要放在view的updateUI
中。比如从服务器拉取到的是商品的状态,然后根据商品状态button展示不同的title。
//写法一 处理过程放在UI中
@interface XXCellViewModel
@property (nonatomic, assign) kGoodsState state;
@end
@implementation XXCell
- (void)updateUI:(XXCellViewModel *)viewModel {
swith(viewModel.state) {
case kGoodsStateBuy:
[self.btn setTitle:@"购买" forState:UIControlStateNormal];
break;
case kGoodsStateCollect:
[self.btn setTitle:@"收藏" forState:UIControlStateNormal];
break;
...
}
}
@end
//写法二 处理过程放在生成cellViewModel的地方
@implementation XXPageViewModel
- (void)transformServerModel:(ServerModel *)serverModel {
XXCellViewModel *cellModel = [[XXCellViewModel alloc] init];
swith(serverModel.state) {
case kGoodsStateBuy:
cellModel.btnTitle = @"购买";
break;
case kGoodsStateCollect:
cellModel.btnTitle = @"收藏";
break;
...
}
}
@end
//写法三 处理过程放在UI对应的viewModel中
@interface XXCellViewModel
@property (nonatomic, copy) NSString *btnTitle;
@end
@implementation XXCellViewModel
- (instancetype)initWithServerModel:(ServerModel *)serverModel {
swith(serverModel.state) {
case kGoodsStateBuy:
_btnTitle = @"购买";
break;
case kGoodsStateCollect:
_btnTitle = @"收藏";
break;
...
}
//如果处理过程比较耗时,也可以用懒加载,重写get方法
}
@end
@implementation XXCell
- (void)updateUI:(XXCellViewModel *)viewModel {
[self.btn setTitle:viewModel.btnTitle forState:UIControlStateNormal];
}
@end
显然第三种写法,是最好维护的,也是最好测试的。cellViewModel要为UI展示做好准备,而这个处理过程最好封装在其内部。
一个方法里处理不止一件事
如果你想让你的代码只有你一个人能搞懂,那这是个极好的技巧,这么干,说不定有一天你自己都不明白了。在一个方法里处理不止一件事,我总结大致有两种情况,一种是几件事有一个共同的处理逻辑,于是放在了一个方法里,把不同的部分用if else区分。一种情况是你处理着某个逻辑呢,正好顺便把其他的事情也一起干了,不知不觉就干了几件事。
//第一种情况
- (void)doSomethingWithType:(SomeType)type {
NSString *str = @"";
if (type == SomeType1) {
str = @"123";
} else if (type == SomeType2) {
str = @"456";
} else {
...
}
//use `str` to do the common things
}
//第二种情况,比如获取缓存数据
- (NSArray *)getCachedData {
if (currentUserId == cachedUserId) {
return cachedData;
} else {
[self clearCachedData];
}
}
针对第一种情况,可以将共同的部分封装成一个方法,留出参数让调用者去传。不同的情况封装成不同的有针对性的方法,然后使用时,在各自情况处调用各自的方法。不要嫌麻烦,你应该尽可能减少if else的出现。
对于第二种情况,正如你的方法名一样,它是要干什么的就只干什么,不要偷偷的又做了其他事情。像上面例子中的,如果要清除之前登录过的用户的数据,可以选择其他时机处理,比如登录之后,或者初始化时比对一下。如果非要做两件事,那要把方法名取好,以免误导调用者。
方法用NSDictionary包装不同的参数
这也是一种非常容易把人搞的一头雾水的办法,你的dictionary里放的什么,如果没有注释,不看方法实现过程,别人根本猜不到。用代码来展示一下这种情况吧.
- (void)doSomethingWithDict:(NSDictionary *)dict {
if ([dict[@"type"] isEqualToString:@"1"]) {
NSString *param1 = dict[@"param1"];
//use param1 to do something here
} else if ([dict[@"type"] isEqualToString:@"1"]) {
NSString *param2 = dict[@"param2"];
//use param2 to do another thing here
} else {
....
}
// do the some common things
}
这种情况其实也可以像上面提到的第一种情况那样,将该方法进行拆分,如果觉得实在没必要或者只能这样做,也请把方法的参数都有啥一一在方法定义中列清楚,有type时,用enum定义好。
弄一个魔法全局变量,在几个方法中改变其值
如果是一个全局的BOOL变量,为了做某种情况的标识,在几个方法中改变其值,是可以理解的。但是如果弄一个变量用来统计计数,你还在好几个方法中去改变其值就比较糟糕了。这让别人看你代码时会很蒙,都没办法预料到你在哪还会改变它的值。这些方法变得不够纯粹,如果不小心被调用了多次,那伴随着这个魔法值也有可能被计算错。
如果发生了这种情况,请自检一下整体解决问题的思路是否合理,还能不能通过其他手段来得到这个统计值,以及这个值存在的必要性。
不管界面由几部分数据源组成,都只用一个Section
可以有100种方法,把设计稿上的样式开发出来,但我们仍然要去思考其最优解。项目中常用到的就是tableView
和collectionView
,如果你不管界面是由几部分数据源组成的,都只用一个section去做,有时往往会把事情变得很复杂。
- 可能要把本可以作为
sectionHeader
和sectionFooter
的部分包装成cell,而这些cell可能根本不需要数据来刷新,展示的内容是死的。 - 两个部分的model不同,所以
dataSourceArray
只能存id类型,再用的时候要一直去if else来判断。 - 由于数据源来自不同的接口,为了保证
dataSourceArray
中的顺序,要把不同的接口有顺序的去请求,但是如果是分开的section,就可以回来一部分数据刷新一部分界面了。 - 在做曝光买点时,涉及到一些计算,由于不知道每一块数据在
dataSourceArray
什么位置,又增加了计算的复杂度。 - .....
如果界面是由几部分不同数据源组成的,多数情况下还是用不同的dataSourceArray
来存储,界面用不同的section去做。
为了方便调用,随意引用
尽量去保证数据是单向流动的,引用也应该尽量保证是单向的,如果为了方便调用,随意引用,很可能造成你中有我我中有你的循环import。比如在moduleviewModel中去引用viewController或者在cellViewModel中去引用cell,那一定是有办法可以消除这种情况的。一旦互相引用,两者强绑定在一起,那都没办法去复用了。
滥用RACCommand
我觉得RAC里最妙的就是Signal
,它让我们省去了写delegate
、block
、notification
的一堆代码。当然了,它还有很多像merge
、combine
这样的操作。所以RAC范畴内的东西,几乎都会和Signal
有关,包括RACCommand
。咱们先来看一个RACCommand
正常来使用的姿势,然后阐明这里所说的“滥用RACCommand”。
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) {
return [client logIn];
}];
[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
[loginSignal subscribeCompleted:^{
NSLog(@"Logged in successfully!");
}];
}];
self.loginButton.rac_command = self.loginCommand;
RACCommand
用一个Signal
来对其初始化,在调用execute
时也返回了Signal
,可以对其监听执行的结果,然后把这个command跟button绑定在一起,当button被点击时就会去执行这个command。当然了,也可以手动调用RACCommand
。可是你如果写成了下面这样......
@interface XXViewModel()
@property (nonatomic, strong) RACCommand *subscribeCommand;
@end
@implementation XXViewModel
- (instancetype)init {
if(self = [super init]) {
_subscribeCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) {
[self doSubscribe];
return [RACSignal empty];
}];
}
return;
}
- (void) doSubscribe {
}
@end
//在调用的地方
[viewModel.subscribeCommand excute:nil];
看出问题了吗?这样用,跟我直接调用[viewModel doSubscribe]
有什么区别!在viewModel中把要执行的方法都这样用RACCommand
包装一下,看似用RAC,实际它的一点简便性都没用到。
几点不成熟的小建议
我们很多人在工作中可能更多的时候都是在做业务功能,业务一复杂,代码的复杂度也上来了,而且业务还是经常容易变更的,所以我们要尽可能的把代码写的清晰明了。几点不成熟的小建议吧:
- 从整体中分离出去,把一类的东西进行封装,大到层与层之间,小到一个方法的实现。还是那句老话,高内聚、低耦合。
- 避免if else的层级嵌套,一旦多了,就说明有可能你在一个方法块中处理了很多事情。
- 不要怕多写几个类,多写几个方法,其实就按照逻辑本身的样子一点点往下走就行。
- 不要去copy、paste,说出来都知道,可就是控制不住自己的手呢。
最近在看一本叫《股票作手回忆录》的书,里面有这样一个注解:
逆反行为和从众行为一样愚蠢。我们需要的是思考,而不是投票表决。不幸的是,伯特兰罗素对于普通生活的观察又在金融界中神奇地应验了:“大多数人宁愿去死也不愿意去思考。许多人真的这样做了。” ————巴菲特
--End--
网友评论