对实践 去Model化的一点体验

前言

在实践 casa 的去Model化后,为了解决网络数据获取后如何处理、如何更新、如何监听模型数据变化等一系列细节问题,而设计了AGViewModel。
其主要作用就是管理视图与数据之间的关系,简化工作,尽量少做重复事情。
其实从减少工程的业务代码量来说是很可观的,开发效率提高不少。但是对于这样的设计会不会带来测试上的问题,或者一些我没有注意到的架构上的缺陷?由于经验有限,还希望发现的朋友批评指出。

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 sizeForLayout:(UIScreen *)screen
{
  // size
  
}

还能做什么?

把视图的各种用户操作传递给代理处理(一般都是Controller)

  • 传递事件
  • 参考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 中删除书本

键值观察 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容