前言
在实践 casa 的去 Model 化后, 为了解决网络数据获取后如何处理, 如何更新, 如何监听模型数据变化等一系列细节问题, 而设计了 AGViewModel.
其主要作用就是管理视图与数据之间的关系, 简化工作, 尽量少做重复事情.
其实从减少工程的业务代码量来说是很可观的, 开发效率提高不少. 但是对于这样的设计会不会带来测试上的问题, 或者一些我没有注意到的架构上的缺陷? 由于经验有限, 还希望发现的朋友批评指出.
https://github.com/JohnnyB0Y/AGViewModel 的设计思路
持有一个字典数据模型, 并弱引用视图.==> View <.. AGViewModel -> Model
用户通过 AGViewModel 对字典模型进行数据增删改查.
数据改变后, 用户可以通过 AGViewModel 通知视图更新自己的 Size 或刷新 UI 界面.
具体怎样计算 Size 和怎样显示 UI 由视图自己决定.(当需要这么做的时候, AGViewModel 会把数据提供给视图)
当视图产生了用户的点击事件时, 视图通过 AGViewModel 把事件传递给 ViewController 处理.
用户对数据的操作和对事件的处理都是在 ViewController 上进行的.(对, 不要忘了 ViewController 也要干活)
大体关系是:
View - 显示 UI, 接收 UI 事件; ViewModel - 协调视图和控制器干活; Model-;Controller - 处理数据和 UI 事件.
AGViewModel 对视图 Size 的管理方法.
- /** 获取 bindingView 的 size, 从缓存中取. 如果有 "需要缓存的视图 Size" 的标记, 重新计算并缓存.*/
- - (CGSize) ag_sizeOfBindingView;
- /** 直接传进去计算并返回视图 Size, 如果有 "需要缓存的视图 Size" 的标记, 重新计算并缓存.*/
- - (CGSize) ag_sizeForBindingView:(UIView<AGVMIncludable> *)bv;
- /** 计算并缓存绑定视图的 Size */
- - (CGSize) ag_cachedSizeByBindingView:(UIView<AGVMIncludable> *)bv;
- /** 对 "需要缓存的视图 Size" 进行标记; 当调用获取视图 Size 的方法时, 从视图中取.*/
- - (void) ag_setNeedsCachedBindingViewSize;
- /** 如果有 "需要缓存的视图 Size" 的标记, 重新计算并缓存.*/
- - (void) ag_cachedBindingViewSizeIfNeeded;
AGViewModel 对视图 UI 的管理方法.
- /** 马上更新数据 并 刷新视图 */
- - (void) ag_refreshUIByUpdateModelInBlock:(nullable NS_NOESCAPE AGVMUpdateModelBlock)block;
- /** 更新数据, 并对 "需要刷新 UI" 进行标记; 当调用 ag_refreshUIIfNeeded 时, 刷新 UI 界面.*/
- - (void) ag_setNeedsRefreshUIModelInBlock:(nullable NS_NOESCAPE AGVMUpdateModelBlock)block;
- /** 对 "需要刷新 UI" 进行标记; 当调用 ag_refreshUIIfNeeded 时, 刷新 UI 界面.*/
- - (void) ag_setNeedsRefreshUI;
- /** 刷新 UI 界面.*/
- - (void) ag_refreshUI;
- /** 如果有 "需要刷新 UI" 的标记, 马上刷新界面. */
- - (void) ag_refreshUIIfNeeded;
视图要做的两件事
- // 设置模型数据, 更新 UI
- - (void)setViewModel:(AGViewModel *)viewModel
- {
- [super setViewModel:viewModel];
- // TODO
- }
- // 计算返回自己的 Size
- - (CGSize) ag_viewModel:(AGViewModel *)vm sizeForBindingView:(UIScreen *)screen
- {
- // size
- }
还能能做什么?
作为本级控制器与下级控制器之间的桥梁
传递数据
参考 Demo 中删除书本
键值观察 KVO
观察内部字典数据的变化
传递响应事件
参考 Demo 中删除书本
数据归档
根据你需要提取的 keys, 把数据归档写入文件
- NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.tableViewManager.vmm];
- [data writeToURL:[self _archiveURL] atomically:YES];
JSON 转换
根据你需要提取的 keys, 拼接成 JSON 字符串 (后台说: 你直接扔个 JSON 给我, 接口写不动了)
根据 JSON 转成字典和数组 (提供了 C 函数)
- // 取数据
- NSData *data = [NSData dataWithContentsOfURL:[self _archiveURL]];
- AGVMManager *vmm = [NSKeyedUnarchiver unarchiveObjectWithData:data];
- // JSON
- NSString *JSON = [vmm ag_toJSONString];
- NSError *error;
- NSDictionary *dict = ag_newNSDictionaryWithJSONString(JSON, &error);
对 id 类型数据判断处理
对 API 返回数据的边界判断和处理
参考 AGVMSafeAccessible 协议
类之间的关系
AGViewModel 与 AGVMPackagable 协议 的关系
遵守 AGVMPackagable 协议的类 (简称 VMP), 提供了对 API 数据的处理, 并生成 AGViewModel.
一个视图是可以显示多个 API 数据的, 例如一个参与抽奖的用户和参与投票的用户, 获取数据的 API 不同, 但显示效果是一致的.
那么 VMP 就是为视图服务的, 把各种各样的 API 数据处理成视图可以接受的数据.(视图就是通过 setViewModel: 来更新 UI 界面的)
当一个界面需要显示各种 Cell 的时候, 只要把 API 数据经过不同的 VMP 过滤就可以取出具备显示条件的数据了.
Cell 的复用很简单:
调用类方法 + ag_dequeueCellBy:for: 获取
当然前提是要在前面注册 + ag_registerCellBy:
AGVMPackagable 协议
- @protocol AGVMPackagable <NSObject>
- - (AGViewModel *) ag_packageData:(NSDictionary *)dict forObject:(nullable id)obj;
- @end
视图复用的一些协议已经在分类中实现 (有特殊需求 Override)
- #pragma mark - ------------- ViewModel 相关协议 --------------
- #pragma mark BaseReusable Protocol
- @protocol AGBaseReusable <NSObject>
- @required
- + (NSString *) ag_reuseIdentifier;
- @optional
- /** 如果使用默认 nib,xib 创建视图, 又需要打包成库文件的时候, 请返回你的资源文件目录.*/
- + (NSBundle *) ag_resourceBundle;
- @end
- #pragma mark CollectionViewCell Protocol
- @protocol AGCollectionCellReusable <AGBaseReusable>
- @required
- + (void) ag_registerCellBy:(UICollectionView *)collectionView;
- + (__kindof UICollectionViewCell *) ag_dequeueCellBy:(UICollectionView *)collectionView
- for:(nullable NSIndexPath *)indexPath;
- @end
- #pragma mark HeaderViewReusable Protocol
- @protocol AGCollectionHeaderViewReusable <AGBaseReusable>
- @required
- + (void) ag_registerHeaderViewBy:(UICollectionView *)collectionView;
- + (__kindof UICollectionReusableView *) ag_dequeueHeaderViewBy:(UICollectionView *)collectionView
- for:(nullable NSIndexPath *)indexPath;
- @end
- #pragma mark FooterViewReusable Protocol
- @protocol AGCollectionFooterViewReusable <AGBaseReusable>
- @required
- + (void) ag_registerFooterViewBy:(UICollectionView *)collectionView;
- + (__kindof UICollectionReusableView *) ag_dequeueFooterViewBy:(UICollectionView *)collectionView
- for:(nullable NSIndexPath *)indexPath;
- @end
- #pragma mark TableViewCell Protocol
- @protocol AGTableCellReusable <AGBaseReusable>
- @required
- + (void) ag_registerCellBy:(UITableView *)tableView;
- + (__kindof UITableViewCell *) ag_dequeueCellBy:(UITableView *)tableView
- for:(nullable NSIndexPath *)indexPath;
- @end
- #pragma mark HeaderFooterViewReusable Protocol
- @protocol AGTableHeaderFooterViewReusable <AGBaseReusable>
- @required
- + (void) ag_registerHeaderFooterViewBy:(UITableView *)tableView;
- + (__kindof UITableViewHeaderFooterView *) ag_dequeueHeaderFooterViewBy:(UITableView *)tableView;
- @end
AGViewModel 与 AGVMSection,AGVMManager 的关系
简单来说就是 AGVMSection 管理一组 AGViewModel,AGVMManager 管理多组 AGViewModel(就是管理多个 AGVMSection).
从视图层面说, 就是:
AGViewModel 能够管理 TableViewCell 的数据.
AGVMSection 能够管理 一组 TableViewCell 和 HeaderView,FooterView 的数据.
AGVMManager 能够管理 整个 TableView 的数据;
AGVMNotifier 与 AGViewModel 的关系
当用户需要监听 AGViewModel 的通用字典数据变化时, 后台操作就是由 AGVMNotifier 提供的.
-AGVMNotifier 是通过 KVO 来处理监听事件的. AGVMNotifier 同时也处理了 KVO 的崩溃问题.
AGVMPackager 与 AGViewModel 的关系
AGVMPackager 就是方便打包 AGViewModel 数据的单例.
AGVMPackager 可以对数据进行打印, 打印出需要的代码.
打印需要的代码
- /**
- 分解打印 JSON 为常量.(嵌套支持)
- @param object 待分解的字典或数组
- @param moduleName 模块的名称
- */
- - (void) ag_resolveStaticKeyFromObject:(id)object
- moduleName:(NSString *)moduleName;
Debug 查看内部数据结构
断点查看数据对象内部信息
数据对象内部信息
CocoaPods 集成
- platform :iOS, '7.0'
- target 'AGViewModel' do
- pod 'AGViewModel'
- end
来源: http://www.jianshu.com/p/9ebd505ade1b