MVVM 是 MVC 模式的一种演进,它主要解决了 ViewController 过于臃肿带来的不易维护和测试的问题。其中 viewmodel 的主要职责是处理业务逻辑并提供 View 所需的数据,这样 VC 就不用关心业务,自然也就瘦了下来。viewmodel 只关心业务数据不关心 View,所以不会与 View 产生耦合,也就更方便进行单元测试。
View 是一个壳,它所呈现的内容都需要由 viewmodel 来提供,而 View 又不与 viewmodel 直接沟通,这时就需要 ViewController 来做中间的协调者。
ViewController 持有 View 和 viewmodel,当 VC 初始化时,会让 viewmodel 去取数据,简单来说就是调用 VM 的某个获取数据的方法。
使用 MVVM 最舒服的姿势是搭配 ReactiveCocoa。不过它的问题在于学习成本和维护成本比较高,在小团队中或许还可以尝试,当开发人员数量较多时就很难推起来了。这也是我们今天要讲的主题:如何不借助 ReactiveCocoa 来实现 MVVM。
先从数据的获取开始说起吧。在 ReactiveCocoa 里有一个类叫「RACCommand」,它的主要作用是执行某个会改变数据的操作,然后提供获取数据的方法,跟我们想要达到的目的很像,所以可以借鉴这个思路,写一个简单的 Command。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
typedef void(^MGJCommandCompletionBlock)(id error, id content); // 1 void(^MGJCommandConsumeBlock)(id input, MGJCommandCompletionBlock completionHandler); // 2 void(^MGJCommandCancelBlock)(); @interface MGJCommandResult : NSObject // 3 @property (nonatomic) NSError *error; // 4 @property (nonatomic) id content; @end @interface MGJCommand : NSObject // 5 @property (nonatomic, readonly) BOOL executing; // 6 readonly) MGJCommandResult *result; - (instancetype)initWithConsumeHandler:(MGJCommandConsumeBlock )consumeHandler; // 7 consumeHandler cancelHandler:(MGJCommandCancelBlock )cancelHandler; // 8 - (void)execute:(id)input; // 9 void)cancel; @end |
- @H_301_251@input是外部传过来的值,比如 user_id,当拿到数据后,调用下 completionHandler,这样
result
属性就会变化 - 有些操作,如 http 请求,需要手动取消
- 单独把
error
作为一个属性放出来,是因为很多数据请求操作都可能出错,当出错后,只需改变这个 error 属性即可。 -
content
存放了这个 Command 的数据处理结果。 - 标识了这个 Command 目前的运行状态,比如可以根据这个状态来显示 loading。
- 每次 Command 执行完一个任务后,result 都会改变,外部可以 KVO 这个 result,然后就可以实时获取最新的结果了。
- Command 的执行逻辑,如果实现了
cancelHandler
的话,外部调用cancel
,这个 Handler 就会被触发。 - 外部可以调用这个方法来触发 Command 的执行,同时可以传一个参数进来。
- 外部可以调用这个方法来取消 Command 的执行。
实现起来也蛮简单的,这里就不多说了。用起来大概是这样:
// Someviewmodel.m @weakify(self); self.followCommand = [[MGJCommand alloc] initWithConsumeHandler:^(id input, MGJCommandCompletionBlock completionHandler) { @strongify(self); [FollowRequest getFollowList:(NSDictionary *)input success:^(NSArray *users) { self.usersToFollow = users; completionHandler(nil, kFollowExpertSearchSucceedSignal); } failure:^(StatusEntity *error) { completionHandler(error, nil); }]; }];