ReactiveCocoa2.5基础知识

ReactiveCocoa2.5基础知识

[toc]

ReactiveCocoa 是一个 iOS 中的函数式响应式编程框架,本文中简称RAC。在开发 GitHub for Mac 过程中的一个副产品,它提供了一系列用来组合和转换值流的 API 。本文只是记录自己学到ReactiveCocoa的笔记。

ReactiveCocoa核心组件

信号源

RACStream 有两个派生类

RACSequence :基于空间的数据流(在RAC中很少使用)
创建
    RACSequence *sequence1 = [RACSequence return:@1];
    NSLog(@"%@",sequence1.head);
    // 输出:1
    RACSequence *sequence2 = [RACSequence sequenceWithHeadBlock:^id{
        return @2;
    } tailBlock:^RACSequence *{
        return sequence1;
    }];
    NSLog(@"%@ %@",sequence2.head,sequence2.tail.head);
    // 输出:2 1
    RACSequence *sequence3 = @[@4,@5,@6,@7].rac_sequence;
     for (id value in concatSquence) {
          NSLog(@"%@",value);
    }
    // 输出:4
    // 输出:5
    // 输出:6
    // 输出:7
变换
    RACSequence *mapSrquence = [sequence1 map:^id(NSNumber *value) {
            return @(value.integerValue *3); // 1 * 3
        }];
        NSLog(@"%@",mapSrquence.head);
        // 输出:3
// concat:合并,目的:有两部分数据,想让上部分先执行,完了之后再让下部分执行(都可获取值)
    RACSequence *concatSquence = [sequence2 concat:mapSrquence];
    for (id value in concatSquence) {
        NSLog(@"%@",value);
    }
    // 输出:2
    // 输出:1
    // 输出:3

    concatSquence: 2 1 3
    sequence3    : 4 5 6 7
    RACSequence *zipSquence = [RACSequence zip:@[concatSquence,sequence3]];
    
遍历
for (RACTuple *tuple in zipSquence) {
        NSLog(@"%@---%@",tuple.first,tuple.second);
    }
    // 输出:2---4
    // 输出:1---5
    // 输出:3---6
RACSignal :基于时间的数据流
1.使用基础
- 单元信号
    RACSignal *signal1 = [RACSignal return:@1];
    RACSignal *signal2 = [RACSignal error:errorObject];
    RACSignal *signal3 = [RACSignal empty];
    RACSignal *signal4 = [RACSignal never];
- 动态信号
    RACSignal *signal5 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendNext:@2];
        // 如果出现错误,sendCompleted就会失效
//        [subscriber sendError:errorObject];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
        }];
    }];
- Cocoa桥接
    // 监听
    RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame)];
    // 事件点击
    RACSignal *signal7 = [button rac_signalForControlEvents:UIControlEventTouchUpInside];
    // dealloc
    RACSignal *signal8 = [view rac_willDeallocSignal];
    // KVO
    RACSignal *signal9 = RACObserve(view, backgroundColor);
- 信号变换
    RACSignal *signal10 = [signal1 map:^id(NSString *value) {
        return [value substringFromIndex:1];
    }];
- 序列转换
  RACSequence *sequence = [RACSequence return:@1];
    RACSignal *signal11 = sequence.signal;
2.订阅一个信号的方式

2.1 ) 订阅方法

// 有三种参数方法
- (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;
 example:
 [signal11 subscribeNext:^(id x) {
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        NSLog(@"error");
    } completed:^{
        NSLog(@"finished");
    }];

2.2 ) 绑定

RAC(view,backgroundColor) = signal10;

2.3 ) Cocoa桥接

// rac_liftSelector: 相当于多线程组;所有信号请求完成之后再执行方法,只需把三个结果传出去即可
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignals:signal1,signal3, nil];
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[signal1,signal1]];
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalOfArguments:signal1];
3.单个信号的变换

3.1 ) 值操作

  • Map:映射,目的:把原始值value映射成一个新值


   RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       [subscriber sendNext:@1];
       [subscriber sendNext:@2];
       [subscriber sendNext:@3];
       [subscriber sendCompleted];
       return nil;
   }];
   RACSignal *signalB = [signalA map:^id(NSNumber *value) {
       return @(value.integerValue * 3);
   }];
   [signalB subscribeNext:^(id x) {
       NSLog(@"%@",x);
   }];
   // 输出:3
   // 输出:6
   // 输出:9
  • mapReplace:Map简化操作,只返回一个相同的信号
RACSignal *signalC = [signalA mapReplace:@8];
   [signalC subscribeNext:^(id x) {
       NSLog(@"%@",x);
   }];
   // 输出:8
   // 输出:8
   // 输出:8
  • reduceEach:Map的变体,当 signalA 的值事件包裹的数据是 RACTuple 类型时,才可以使用该操作


RACSignal *signalD = [signalA reduceEach:^id(NSNumber *num1 , NSNumber *num2){
        return @([num1 intValue] + [num2 intValue]);
    }];
  • not:需要传入的值都是NSNumber类型的,不是NSNumber类型会报错
  • not操作会把每个NSNumber按照BOOL的规则,取非,当成新信号的值。
- (RACSignal *)not {
    return [[self map:^(NSNumber *value) {
        NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value);
        return @(!value.boolValue);
    }] setNameWithFormat:@"[%@] -not", self.name];
}
  • and操作需要原信号的每个信号都是元组RACTuple类型的,因为只有这样,RACTuple类型里面的每个元素的值才能进行&运算。
  • and操作里面有3处断言。第一处,判断入参是不是元组RACTuple类型的。第二处,判断RACTuple类型里面至少包含一个NSNumber。第三处,判断RACTuple里面是否都是NSNumber类型,有一个不符合,都会报错。
- (RACSignal *)and {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
        return @([tuple.rac_sequence all:^(NSNumber *number) {
            NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
            return number.boolValue;
        }]);
    }] setNameWithFormat:@"[%@] -and", self.name];
}
  • or操作的实现和and操作的实现大体类似。3处断言的作用和and操作完全一致,这里就不再赘述了。or操作的重点在any函数的实现上。or操作的入参也必须是RACTuple类型的。
- (RACSignal *)or {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
        return @([tuple.rac_sequence any:^(NSNumber *number) {
            NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
            return number.boolValue;
        }]);
    }] setNameWithFormat:@"[%@] -or", self.name];
}
  • 这里也有2个断言,第一个是确保传入的参数是RACTuple类型,第二个断言是确保元组RACTuple里面的元素各种至少是2个。因为下面取参数是直接从1号位开始取的。
  • reduceApply做的事情和reduceEach基本是等效的,只不过变换规则的block闭包一个是外部传进去的,一个是直接打包在每个信号元组RACTuple中第0位中。
- (RACSignal *)reduceApply {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]");
        // We can't use -array, because we need to preserve RACTupleNil
        NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count];
        for (id val in tuple) {
            [tupleArray addObject:val];
        }
        RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]];
        return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments];
    }] setNameWithFormat:@"[%@] -reduceApply", self.name];
}
// 举个例子
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
                              id block = ^id(NSNumber *first,NSNumber *second,NSNumber *third) {
                                  return @(first.integerValue + second.integerValue * third.integerValue);
                              };
                              [subscriber sendNext:RACTuplePack(block,@1 , @3 , @5)];
                              [subscriber sendNext:RACTuplePack((id)(^id(NSNumber *x){return @(x.intValue * 10);}),@10,@20,@30)];
                              [subscriber sendCompleted];
                              return [RACDisposable disposableWithBlock:^{
                              }];
                          }];
    RACSignal *signalB = [signalA reduceApply];
    [signalB subscribeNext:^(id x) {
                NSLog(@"%@",x);
            }];
    // 输出:16 = 1 + 3*5
    // 输出:100 = 10 * 10
  • materialize把信号包装成RACEvent类型。
- (RACSignal *)materialize {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            [subscriber sendNext:[RACEvent eventWithValue:x]];
        } error:^(NSError *error) {
            [subscriber sendNext:[RACEvent eventWithError:error]];
            [subscriber sendCompleted];
        } completed:^{
            [subscriber sendNext:RACEvent.completedEvent];
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -materialize", self.name];
}
  • dematerialize操作是materialize的逆向操作。它会把包装成RACEvent信号重新还原为正常的值信号。
- (RACSignal *)dematerialize {
    return [[self bind:^{
        return ^(RACEvent *event, BOOL *stop) {
            switch (event.eventType) {
                case RACEventTypeCompleted:
                    *stop = YES;
                    return [RACSignal empty];
                case RACEventTypeError:
                    *stop = YES;
                    return [RACSignal error:event.error];
                case RACEventTypeNext:
                    return [RACSignal return:event.value];
            }
        };
    }] setNameWithFormat:@"[%@] -dematerialize", self.name];
}

3.2 ) 数量操作


  • Filter:过滤
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"ab"];
        [subscriber sendNext:@"hello"];
        [subscriber sendNext:@"world"];
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];
        return nil;
    }];
    RACSignal *signalC = [signalA filter:^BOOL(NSString *value) {
        return value.length > 3;
    }];
    [signalC subscribeNext:^(id x) { 
        NSLog(@"%@",x);
    }];
// 输出:hello
// 输出:world
  • Ignore:忽略
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"Ab"];
        [subscriber sendNext:@"hello"];
        [subscriber sendNext:@"world"];
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];
        return nil;
    }];
    RACSignal *signalB = [signalA filter:^BOOL(NSString *value) {
        return ![value isEqual:@"ab"];
    }];
    [signalB subscribeNext:^(id x) { 
        NSLog(@"%@",x);
    }];
// 输出:hello
// 输出:world
// 输出:123
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"Ab"];
        [subscriber sendNext:@"hello"];
        [subscriber sendNext:@"world"];
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];
        return nil;
    }];
    RACSignal *signalC = [signalA ignore:@"ab"];
    [signalC subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
// 输出:hello
// 输出:world
// 输出:123
  • ignoreValues:忽略所有的值

  • distinctUntilChanged:读取所有的值

  • Take:取值的数量


  • Skip:跳过


  • StartWith:初始位置添加


  • Repeat:重复说(一直不停止)


  • Retry:遇到错误继续尝试


  • 副作用

  • DoNext:副作用操作,做一些事情

- (RACSignal *)doError:(void (^)(NSError *error))block;
- (RACSignal *)doCompleted:(void (^)(void))block;
- (RACSignal *)initially:(void (^)(void))block;
- (RACSignal *)finally:(void (^)(void))block;
  • Collect收集源信号的所有sendNext事件,直到源信号发出sendComplete事件,才将收集好的sendNext事件发出去


  • Aggregate折叠函数,和Scan这两个操作有些类似,但明显有区别,前者改变了事件流的事件数量,后者没有


  • Scan每次计算结果都会给到一个加和



    3.3 ) 时间操作

  • Delay延迟


  • Throttle:阀门



    3.4 ) 组合操作

  • Concat:连接


  • Merge:汇合


RACSignal *signalC = [signalA merge:signalB];
RACSignal *signalD = [RACSignal merge:@[signalA,signalB]];
RACSignal *signalE = [RACSignal merge:RACTuplePack(signalA,signalB)];
  • zip:拉链,目的:把两个信号压缩成一个信号,并且把两个信号的内容合并成一个元组


RACSignal *signalC = [signalA zipWith:signalB];
RACSignal *signalD = [RACSignal zip:@[signalA,signalB]];
RACSignal *signalE = [RACSignal zip:RACTuplePack(signalA,signalB)];
  • CombineLatest:和数学中的逻辑与(&&)的概念是一样的,只有两个两个信号同时发送了sendNext事件,才会触发


RACSignal *signalC = [signalA combineLatestWith:signalB];
RACSignal *signalD = [RACSignal combineLatest:@[signalA,signalB]];
  • Sample:采样


  • TakeUntil:当下一个信号传递的时候停止订阅信号


  • TakeUntilReplacement:当下一个信号传递的时候停止订阅信号,并返回加和下个信号


两者的区别
Pull-driver vs Push-driver

例子:看电影
Sequence:看本地下载的电影,突然想上厕所,可以暂停,上完厕所回来再看(观看者决定)
Signal:在线看电影,不管你看不看电影一直都在播放(发送者决定)

Data vs Event

Sequence: 任何ID类型的数据(值)
Signal:基于信号(值,错误,终止,中断)

订阅者

RACSubscriber

订阅过程

对于订阅过程不了解可以查看这三篇博客
细说ReactiveCocoa的冷信号与热信号(一)
细说ReactiveCocoa的冷信号与热信号(二)
细说ReactiveCocoa的冷信号与热信号(三)

RACSignal *signal5 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendNext:@2];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"dispose");
        }];
    }];
    RACDisposable *disposable = [signal5 subscribeNext:^(id x) {
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        NSLog(@"error");
    } completed:^{
        NSLog(@"finished");
    }];
    [disposable dispose];

调度器

RACScheduler

RACSchedulerReactiveCocoa 中就是扮演着调度器的角色,本质上,它就是用 GCD 的串行队列来实现的,并且支持取消操作。是的,在 ReactiveCocoa 中,并没有使用到 NSOperationQueueNSRunloop 等技术,RACScheduler 也只是对 GCD 的简单封装而已。

清洁工

RACDisposable

RACDisposableReactiveCocoa 中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose ,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject-dealloc 方法

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

推荐阅读更多精彩内容