ReactiveCocoa理解笔记(2)

MVVM和ReactiveCocoa

在MVVM中UIViewController讲专注的负责View的管理、界面流程跳转,它不会直接持有、处理数据。UIViewController需要准备数据来组织、展现View,这时它需要通过对应的View-Model来准备,数据的获取、处理、持有都由View-Model来负责。估计这时大多数人心里有有了一些想法:View-Model在处理准备好数据以后,完全可以通过代理、block来实现通知UIViewController刷新界面。这个想法也是可以的,不使用ReactiveCocoa也可以实现MVVM开发模式,但是我还是愿意花时间来学习、了解ReactiveCocoa。初始接触ReactiveCocoa时,感觉它是一个很怪异的东东,以前“正”着写的,现在需要“反”着写,语法怪异,理解困难。其他朋友们有必要花时间学习吗?

目前有一个很火的理念:数据与视图绑定。就是当数据变化时,视图不需要额外的处理,便可正确地呈现最新的数据。而这也是ReactiveCocoa最重要的亮点。RAC与MVVM编程模式结合,可以方便的处理界面变化,减少View-ModelUIViewController连接,让我们两层的核心功能。

ReactiveCocoa不是单一功能的模块,它是一个Framework,提供了一整套解决方案。其核心思想是「响应数据的变化」,在这个基础上有了Signal的概念,进而可以帮助减少状态类型,易于使用MVVM架构,方便的响应式编程等等。

为什么要使用ReactiveCocoa

  1. 开发过程中,状态以及状态之间依赖过多,RAC更加有效率地处理事件流,而无需显式去管理状态。在过程式编程中,状态变化是最难跟踪,最头痛的事。这个是我使用RAC最重要的一点。
  • 减少方法的调用,由于它跟踪状态和值的变化,因此不需要状态更新时手动调用,减少出错的可能。
  • 提供统一的消息传递机制,将OC中的通知、代理,KVO以及其它所有UIControl事件的变化都进行监控,当变化发生时,就会传递事件和值。
  • 当值随着事件变换时,可以使用combineLatest、map、filter等函数便利地对值进行变换操作。

比如:我们想要实现一个需求:当“count”变量中的字符串改变后即时做出相应的反馈。用KVO我们一般会这样做

<pre>`
// In your class viewDidLoad/init
[self addObserver:self
forKeyPath:@"count"
options:NSKeyValueObservingOptionNew
context:nil];

// In dealloc
[self removeObserver:self
forKeyPath:@"count"
context:nil];

// rewrite in your class

  • (void)observeValueForKeyPath:(NSString)keyPath
    ofObject:(id)object
    change:(NSDictionary
    )change
    context:(void *)context
    {
    if ([keyPath isEqualToString:@"count"]) {
    //do ....
    }
    }
    `</pre>

如果我们在工程中使用ReactiveCocoa后,只有短短几行,还无需重写方法
<pre>[RACObserve(self, count) subscribeNext:^(NSNumber *count) { //do .... }];</pre>

看到RAC的实现是不是觉得很简单,代码简洁清晰,下面了解一下RAC

RACSignal

RAC为应用中发生的不同事件流提供了一个标准接口。在ReactiveCocoa术语中这个叫做信号,由RACSignal类表示。RAC的核心就是RACSignal发送事件流给它的订阅者,共有三种类型的事件:nexterrorcompleted。一个signal可以在completed前发送任意数量的next事件;也会因为error终止。简单的说就是errorcompleted只能发送一次,在completed前可以发送多次next事件。

信号订阅

信号是一个发送一连串信息的载体体. 如果它还没有订阅者,那么它就是一个冷信号,现在不会起任何作用。只有信号被订阅了,它才会向它的订阅者发送信息,这时信号处于激活状态,称之为热信号

冷信号默认什么也不干,所以我们需要避免在开发过程中创建一堆冷信号,比如下面这段代码,如果不被订阅,或者不被连接,那么它没有任何意义。
<pre>RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { NSLog(@"signal"); [subscriber sendNext:@"1"]; [subscriber sendNext:@"2"]; [subscriber sendCompleted]; return nil; }];</pre>

我们已经创建了一个冷信号signal,但因为没有被subscribe或连接,所以什么也不会发生。
加了下面这段代码后,signal就处于激活状态了,block里的代码就会被执行。

订阅示例:
<pre>[signal subscribeNext:^(NSString *name):^{ NSLog(@"name = %@", name); }];</pre>

连接示例:
<pre>RAC(self.usernameField, text) = RACObserve(self, name);</pre>
RACSignal有很多方法可以来订阅不同的事件类型。每个方法都需要至少一个block,当事件发生时就会执行block中的逻辑。比如每次next事件发生时,subscribeNext:方法提供的block都会执行。

<blockquote>

  • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;

  • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;

  • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;

  • (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock;

  • (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock;

  • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock;

  • (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;

</blockquote>

ReactiveCocoa框架使用category来为很多基本UIKit控件添加signal。这样你就能给控件添加订阅了,如UITextField添加了rac_textSignal

创建RACSignal

使用

+(RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe

方法创建信号,在block需要实现“RACSubscriber”协议,最后还需返回RACDisposable,用于处理、销毁信号。

<pre>`
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) {
NSLog(@"signal");
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendCompleted];
return nil;
}];

[signal subscribeNext:^(NSString *name):^{
NSLog(@"name = %@", name);
}];
`</pre>

使用“RACObserve”宏定义快捷创建信号
<pre>[RACObserve(self, name) subscribeNext:^(NSString *name) { NSLog(@"count %@", name); }];</pre>

这些是一些简单的用法,下面给一个实用的,在开发过程中可以实用的示例,这是一个根据URL获取数据的RACSignal创建示例:

<pre>`

pragma mark - Get请求

  • (RACSignal *)fetchGetServerFromURL:(NSURL *)url
    {

    GLogString(url.description);
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

      //申明请求的数据是json类型
      manager.requestSerializer = [AFJSONRequestSerializer serializer];
      manager.requestSerializer.timeoutInterval = 30.0f;
      
      //申明返回的结果是json类型
      manager.responseSerializer = [AFJSONResponseSerializer serializer];
      
      //如果报接受类型不一致请替换一致text/html或别的
      manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
      
      AFHTTPRequestOperation *operation = [manager GET:url.absoluteString
                                            parameters:nil
                                               success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                  GLogString(responseObject);
                                  MBAResponse *response = [MBAResponse responseWithJsonDict:responseObject];
                                  if (response.requestSuccess) {
                                      [subscriber sendNext:response.dataDict];
                                      [subscriber sendCompleted];
                                  }
                                  else {
                                      GLogString(response.error.localizedDescription);
                                      [subscriber sendError:response.error];
                                  }
                                  
                              } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                  [subscriber sendError:error];
                              }];
      
      return [RACDisposable disposableWithBlock:^{
          //信号销毁时,取消请求
          [operation cancel];
      }];
    

    }];
    }
    `</pre>

信号创建以后,订阅者可以获取数据
<pre>`
//创建获取数据的信号
RACSignal *signal = [self fetchGetServerFromURL:[NSURL URLWithString:@"http://www.sina.com"]];

//订阅信号,编写信号数据流处理
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"data= %@", x);
} error:^(NSError *error) {
NSLog(@"error = %@", error);
}];
`</pre>

在请求的过程中,我们如果不需要了,也可以取消请求
<pre>`
//创建获取数据的信号
RACSignal *signal = [self fetchGetServerFromURL:[NSURL URLWithString:@"http://www.sina.com"]];

//订阅信号,编写信号数据流处理
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"data= %@", x);
} error:^(NSError *error) {
NSLog(@"error = %@", error);
}];

//取消请求
if (!disposable.isDisposed) {
[disposable dispose];
}
`</pre>

当我们手动销毁信号,这时之前创建信号时生成的RACDisposable对象就会生效
<pre>return [RACDisposable disposableWithBlock:^{ //信号销毁时,取消请求 [operation cancel]; }];</pre>

这一连串代码示例就是信号创建-订阅-处理(或取消)的过程,只要先理解清楚信号的工作原理,在后面的学习、应用中,会发现RAC更加便捷,更加简单。

总结

这张主要说了RAC的优点以及基本用法,这些基本用法能应对开发过程中各种复杂的需求吗,下面会说说RAC的一下高级用法。

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

推荐阅读更多精彩内容