MVVM与RAC(吹水篇)

前言

首先MVC没什么不好,MVVM也没多么伟大,如果你愿意,可以把MVVM理解为特殊的MVC,就像等边三角形、直角三角形等与普通的三角形关系一样。MVVM是由MVC演变而来的,我们可以在MVC的基础上创建属于自己的开发模式。Massive View Controller?未必!controller作为viewmodel的管理者做好自己的事情就行(Single responsibility principle

  • 创建viewmodel
  • 管理view的生命周期
  • 管理viewmodel的交互逻辑
  • 监听view事件并传给model
  • 监听model变化并更新view

创建viewmodel很简单,在controller初始化的时候就可以做,关于复杂界面的UI完全可以抽象出一个view在内部完成然后再添加到controller。监听viewmodel事件的代码怎么写?抽成一个方法/属性,在controller中调用/赋值就可以了,这样一来controller中也就那么几句代码。如viewmodel网络层存储层服务层的配合,datasourcedelegatecontroller中分离,使用protocolcategoryaspect等。当然,分离、封装这种事儿过犹不及,要把握好度。最终的目的都是DRY(Don't repeat yourself ),做好自己的事、内聚、解耦、复用。然而鱼和熊掌不可兼得,怎样取舍视具体项目而定。无论MVCS还是MVVM或者MVPVIPER等等都是由MVC演变过来的,只是招式,都是为了处理model-view-controller之间的关系,不必生搬硬套,更重要的是如何处理复杂场景的业务逻辑,如何更好的DRY


MVVM

  • MVVM or MVCVM?

先说说胖瘦model。胖model中不单有数据也有处理数据的方法,瘦model中只有数据。我们常用的MVC中的model通常都是瘦model(实际上更像MVCS),而MVVM是基于胖model构建的。MVVM只有VMV自然是viewview controllerM就是胖model胖model又可拆分成modelview model。与其说MVVM,倒不如说MVCVM更为贴切。这样一来,MVVM不过是把MVCC的交互逻辑拿出来放到VM中去,从而达到Lighter View Controllers的目的。按照MVCVM来理解,MVVMcontroller中数据处理的业务逻辑转移到view model确实可以让controller更专注的做自己,但是当业务逻辑很复杂同样会使view model中凝聚大量代码,此时view model也需要做进一步的细化。

  • MVVM的核心思想

MVVM弱化controller,强调view modelviewbindcontroller中的业务逻辑尽量转移到view model中,除开管理view的生命周期不谈,controller只是起到协调view modelview的作用。view model的主要职责是处理业务逻辑并给view提供数据,view model不关心view从而解耦也方便做单元测试。另外,view model中不能有view但是可以有其他view model
用链状结构表示大概长这样:

View/ViewController -> ViewModel ->Model

OK,controller持有viewview modelview model处理业务逻辑并提供model,那么如何将model中的数据展示到view上?这就是前面提到的将view modelview进行绑定,这个操作是在controller中完成的,也就是controller的职责之一,协调view modelview

整个流程还是挺眼熟的,跟MVC很相似。

  • View Model 与 View 的绑定

view modelview绑定说白了就是让view model告诉view,现在数据变了,你该显示不同的内容。但是view model不可以持有view,因此赋值操作无法写在view model里而是在controller中完成。其实可选的方法很多,blockdelegatenotificationKVOtarget-action/invocation...

如果非要说优雅这个词,不谈RAC似乎显得太low,但是就像前面所说,可供选择的方法很多,不是非RAC不可。

RAC

说起RAC不得不说Stream这个概念,stream翻译过来是的意思,用来组词我们最先想到可能是水流电流,会有动态的画面感,流动即驱动,驱动的原动力是事件流RAC就是基于stream这个概念发展来的,即所谓的函数响应式编程( Functional Reactive Programming:FRP )

  • RACStream

RACStream有两个子类:RACSignalRACSequenceSignal表示信号Sequence表示序列,两者都可以很好的描述所拥有的特性。

信号可发送、可接收、可合并、可压缩、可增删改查、可...。这些特性流同样拥有,信号即流,万物皆流,拥有流即可改造万物。

诗云:江河入海流序列可理解为大海,海纳百川序列可以汇集,形成一个更庞大的,也可以分离出所汇集的每一个

  • RACSignal

signal存在的意义就是加工并传递数据,所以signal需要一个订阅者subscriber来接收数据。有subscribersignal称为热信号,没有subscribersignal称为冷信号,没有意义。

举个栗子:

我是栗子🌰

很方便吧,然而并没有什么...用!这是怎么做到的?刚才不是还说需要subscriber吗,为啥没见到?贴一下源码:

@implementation RACSignal
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
 NSCParameterAssert(nextBlock != NULL);
 
 RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
 return [self subscribe:o];
}

@implementation RACSubscriber
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
 RACSubscriber *subscriber = [[self alloc] init];

 subscriber->_next = [next copy];
 subscriber->_error = [error copy];
 subscriber->_completed = [completed copy];

 return subscriber;
}

@implementation UITextField (RACSignalSupport)
- (RACSignal *)rac_textSignal {
 @weakify(self);
 return [[[[[RACSignal
  defer:^{
   @strongify(self);
   return [RACSignal return:self];
  }]
  concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
  map:^(UITextField *x) {
   return x.text;
  }]
  takeUntil:self.rac_willDeallocSignal]
  setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)];
}

可见,这个方法内部已经配置了订阅者,所以可以直接工作。订阅者内部拷贝nexterrorcompleted这三个block,其中errorcompletedNULLnext是基于textField的UIControlEventAllEditingEvents事件触发的,block内参数为textField.text。至此,从心理上起码可以接受RAC这种东西。

凡事要学会抓主要矛盾,切勿眉毛胡子一把抓。先别管代码里种种看不明白的天书,来看看这个方法做了什么,为啥可以监听UIControlEventAllEditingEvents事件

[self rac_signalForControlEvents:UIControlEventAllEditingEvents]

@implementation UIControl (RACSignalSupport)

- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents {
 @weakify(self);

 return [[RACSignal
  createSignal:^(id<RACSubscriber> subscriber) {
   @strongify(self);

   [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
   [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
    [subscriber sendCompleted];
   }]];

   return [RACDisposable disposableWithBlock:^{
    @strongify(self);
    [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
   }];
  }]
  setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", RACDescription(self), (unsigned long)controlEvents];
}

看到了啥?UIControl的分类以及target-actiontargetRACSubscriber的实例,action则为RACSubscriber的协议方法- (void)sendNext:(id)value。Let's go on!

@implementation RACSubscriber
- (void)sendNext:(id)value {
 @synchronized (self) {
  void (^nextBlock)(id) = [self.next copy];
  if (nextBlock == nil) return;

  nextBlock(value);
 }
}

拷贝self.next后执行nextBlock这个block。那么self.next又是啥?就是例子开始中的block

[_textField.rac_textSignal subscribeNext:^(id x) {
  NSLog(@"%@", x);
}];

再来理一遍思路看看RAC是如何一句代码完成对textFieldUIControlEventAllEditingEvents事件监听的。

1 将textField信号化并通过block将target-action转移到订阅者
2 创建订阅者并在订阅者内部拷贝外部subscribeNext中的block
3 当触发UIControlEventAllEditingEvents事件通过订阅者回调外部block


如果你已经理解这些,我们继续吧。回到这个方法,来看看天书们是否安好

@implementation UITextField (RACSignalSupport)

- (RACSignal *)rac_textSignal {
 @weakify(self);
 return [[[[[RACSignal
  defer:^{
   @strongify(self);
   return [RACSignal return:self];
  }]
  concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
  map:^(UITextField *x) {
   return x.text;
  }]
  takeUntil:self.rac_willDeallocSignal]
  setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)];
}
@interface RACSignal (Operations)
// 创建一个信号,当有订阅者订阅信号,将冷信号转换为热信号
+ (RACSignal *)defer:(RACSignal * (^)(void))block;

@interface RACSignal (RACStream)
// 将信号传递给value
+ (RACSignal *)return:(id)value;
// 衔接新旧信号,旧信号执行完执行新信号
- (RACSignal *)concat:(RACSignal *)signal;

@interface RACStream (Operations)
// 信号映射
- (instancetype)map:(id (^)(id value))block;
// 当`signalTrigger`执行`next`或`completed`时,返回信号执行`completed`
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger;

什么乱七八糟的。。!先别想这么多,试着结合上文把刚刚看到的注释拼接起来带入- (RACSignal *)rac_textSignal中可以得到这么一句话:

创建一个可变为热信号的冷信号,并将这个信号传递给textField,当信号传递完毕,利用target-action让订阅者监听UIControlEventAllEditingEvents事件,映射当前信号并作将textField.text做为block参数返回,这一系列操作直到textField.rac_willDeallocSignal执行next或者completed时结束。

那么这句代码的意思已经很清楚了

[_textField.rac_textSignal subscribeNext:^(id x) {
   NSLog(@"%@", x);
}];

_textField.rac_textSignal完成事件监听并管理信号的生命周期,subscribeNext创建订阅者订阅_textField.rac_textSignal监听事件。

似乎有点偏离主题,关于更多RAC知识,下篇文章接着说。

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

推荐阅读更多精彩内容