platform :ios, '7.0'
target 'AGViewModel' do
pod 'AGViewModel', '~> 0.6'
end
在实践 casa 的去Model化后,为了解决网络数据获取后如何处理、如何更新、如何监听模型数据变化等一系列细节问题,而设计了AGViewModel。 其主要作用就是管理视图与数据之间的关系,简化工作,尽量少做重复事情。
- 用户通过AGViewModel 对字典模型进行数据增删改查。
- 数据改变后,用户可以通过AGViewModel 通知视图更新自己的Size 或刷新UI界面。
- 具体怎样计算Size 和怎样显示UI 由视图自己决定。(当需要这么做的时候,AGViewModel会把数据提供给视图)
- 当视图产生了用户的点击事件时,视图通过AGViewModel 把事件传递给 ViewController 处理。
- 用户对数据的操作和对事件的处理都是在 ViewController 上进行的。(对,不要忘了 ViewController也要干活)
- 大体关系是:
- View-显示UI,接收UI事件;ViewModel-协调视图和控制器干活;Model-🌚;Controller-处理数据和UI事件。
/** 获取 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;
/** 马上更新数据 并 刷新视图 */
- (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 sizeForLayout:(UIScreen *)screen
{
// size
}
- 传递事件
- 参考Demo 中点击书本封面
@protocol AGVMDelegate <NSObject>
/**
通过 viewModel 的 @selector(ag_makeDelegateHandleAction:) 方法通知 delegate 做事。
通过 viewModel 的 @selector(ag_makeDelegateHandleAction:info:) 方法通知 delegate 做事。
*/
@optional
- (void) ag_viewModel:(AGViewModel *)vm handleAction:(nullable SEL)action;
- (void) ag_viewModel:(AGViewModel *)vm handleAction:(nullable SEL)action info:(nullable AGViewModel *)info;
@end
- 传递数据
- 参考Demo 中删除书本
- 观察内部字典数据的变化
- 传递响应事件
- 参考Demo 中删除书本
- 根据你需要提取的keys,把数据归档写入文件
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.tableViewManager.vmm];
[data writeToURL:[self _archiveURL] atomically:YES];
- 根据你需要提取的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);
- 不想强引用对象的时候,可以使用。
/** 添加弱引用的对象 */
- (void)ag_setWeaklyObject:(nullable id)obj forKey:(NSString *)key;
/** 获取弱引用的对象 */
- (nullable id) ag_weaklyObjectForKey:(NSString *)key;
/** 移除弱引用的对象 */
- (void) ag_removeWeaklyObjectForKey:(NSString *)key;
- 当需要根据几个变化属性,拿到结果时,可以使用命令编程。
/** 添加命令 */
- (void)ag_setCommand:(AGVMCommand *)command forKey:(NSString *)key;
/** 添加命令Block */
- (void)ag_setCommandBlock:(AGVMCommandExecutableBlock)block forKey:(NSString *)key;
/** 移除命令 */
- (void) ag_removeCommandForKey:(NSString *)key;
/** 获取命令执行结果,直接执行 */
- (nullable id) ag_executeCommandForKey:(NSString *)key;
/** 标记命令待执行 */
- (void) ag_setNeedsExecuteCommandForKey:(NSString *)key;
/** 获取命令执行结果,有标记才执行命令,否则直接从字典中取 */
- (nullable id) ag_executeCommandIfNeededForKey:(NSString *)key;
- 支持一些方便的链式方法调用
- 对API返回数据的边界判断和处理
- 参考 AGVMSafeAccessible 协议
- 遵守AGVMPackagable协议的类(简称VMP),提供了对API数据的处理,并生成 AGViewModel。
- 一个视图是可以显示多个API数据的,例如一个参与抽奖的用户和参与投票的用户,获取数据的API不同,但显示效果是一致的。
- 那么VMP就是为视图服务的,把各种各样的API数据处理成视图可以接受的数据。(视图就是通过setViewModel:来更新UI界面的)
- 当一个界面需要显示各种Cell的时候,只要把API数据经过不同的VMP过滤就可以取出具备显示条件的数据了。
- Cell 的复用很简单:
- 调用类方法+ag_dequeueCellBy:for: 获取
- 当然前提是要在前面注册+ag_registerCellBy:
@protocol AGVMPackagable <NSObject>
- (AGViewModel *) ag_packageData:(NSDictionary *)dict forObject:(nullable id)obj;
@end
@interface UITableView (AGViewModel)
#pragma mark - table view cell
- (void) ag_registerCellForClass:(Class<AGViewReusable>)cls;
- (void) ag_registerNibCellForClass:(Class<AGViewReusable>)cls;
- (__kindof UITableViewCell *) ag_dequeueCellWithClass:(Class<AGViewReusable>)cls for:(nullable NSIndexPath *)indexPath;
#pragma mark - table view header footer view
- (void) ag_registerHeaderFooterViewForClass:(Class<AGViewReusable>)cls;
- (void) ag_registerNibHeaderFooterViewForClass:(Class<AGViewReusable>)cls;
- (__kindof UITableViewHeaderFooterView *) ag_dequeueHeaderFooterViewWithClass:(Class<AGViewReusable>)cls;
@end
@interface UICollectionView (AGViewModel)
#pragma mark collection view cell
- (void) ag_registerCellForClass:(Class<AGViewReusable>)cls;
- (void) ag_registerNibCellForClass:(Class<AGViewReusable>)cls;
- (__kindof UICollectionViewCell *) ag_dequeueCellWithClass:(Class<AGViewReusable>)cls for:(NSIndexPath *)indexPath;
#pragma mark collection view footer view
- (void) ag_registerFooterViewForClass:(Class<AGViewReusable>)cls;
- (void) ag_registerNibFooterViewForClass:(Class<AGViewReusable>)cls;
- (__kindof UICollectionReusableView *) ag_dequeueFooterViewWithClass:(Class<AGViewReusable>)cls for:(NSIndexPath *)indexPath;
#pragma mark collection view header view
- (void) ag_registerHeaderViewForClass:(Class<AGViewReusable>)cls;
- (void) ag_registerNibHeaderViewForClass:(Class<AGViewReusable>)cls;
- (__kindof UICollectionReusableView *) ag_dequeueHeaderViewWithClass:(Class<AGViewReusable>)cls for:(NSIndexPath *)indexPath;
@end
#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
- 简单来说就是 AGVMSection 管理一组 AGViewModel,AGVMManager 管理多组 AGViewModel(就是管理多个AGVMSection)。
- 从视图层面说,就是:
- AGViewModel 能够管理 TableViewCell 的数据。
- AGVMSection 能够管理 一组TableViewCell 和 HeaderView、FooterView 的数据。
- AGVMManager 能够管理 整个TableView 的数据;
- 当用户需要监听 AGViewModel 的通用字典数据变化时,后台操作就是由 AGVMNotifier 提供的。 -AGVMNotifier 是通过KVO 来处理监听事件的。AGVMNotifier 同时也处理了KVO的崩溃问题。
- AGVMSharedPackager 就是方便打包AGViewModel 数据的单例。
- AGVMSharedPackager 可以对数据进行打印,打印出需要的代码。
/**
分解打印 JSON 为常量。(嵌套支持)
@param object 待分解的字典或数组
@param moduleName 模块的名称
*/
- (void) ag_resolveStaticKeyFromObject:(id)object
moduleName:(NSString *)moduleName;