ReactiveCocoa学习之RACSignal

写这篇文章的目的,在于记录自己的学习内容,理顺思路。

RAC(Reactive Cocoa)

Cocoa是苹果整套框架的简称。Reactive响应。
第一次听说RAC,是有人问我,你知道函数响应式编程吗?
RAC就是。

那么,什么是函数响应式编程?
RAC让人觉得厉害的大概就是它的编程思想,事件的处理,经常会有一种让人忍不住赞叹一声,啊!或是一语惊醒梦中人的顿悟感。哈哈,我这个小白,在学习的过程中,是这样子没错啦。

函数式编程:

相关操作使用函数或者方法调用。
特点:每个方法都有返回值,并且使用block或函数作为参数。

顾名思义,采用函数的思想。
函数式编程,多采用闭包与高阶函数,函数是“第一等公民”,惰性计算(只用“表达式”,不用“语句”),递归(无“副作用”),不修改状态(引用透明性)。

例如,数学中常见的函数表达式
y = f(x) ——> y = f(f(x)) ,该表达式的参数是一个函数
应用到OC中,形式就大概如下

- (void)test:(void(^)(NSString *))block{
    
    block = ^(NSString *name){
//表达式
    };
}

响应式编程:

这种于操作结果,而不在意该结果究竟是按照怎样的操作顺序发生变化导致的。有点类似于蝴蝶效应,a发生变化,b自然跟随发生变化。
例如:

int a = 10;
int b = a+10;

我们希望a的值改变时,b的值随之改变,这就是响应,b自然响应a

具体,推荐阅读袁峥大神的https://www.jianshu.com/p/87ef6720a096
几种常见的编程思想介绍具体,而且还有使用例子。

RAC类结构图.jpg

RACSignal

RAC的使用过程,不外乎于
1.信号的产生 2.订阅信号 3.发送信号 4.信号销毁。

//1:信号的产生
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        //3:发送信号
        [subscriber sendNext:@"龙晨"];

        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。

        NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:1008611 userInfo:@{@"key":@"我的错"}];
        [subscriber sendError:error];
        
        //4:信号销毁
        //RACDisposable 信号回收站
        return [RACDisposable disposableWithBlock:^{
 // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            
            // 执行完Block后,当前信号就不在被订阅了。
            NSLog(@"信号销毁了");
        }];
    }];
    
    //2:订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
// block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"%@",x);
    }];
    
    [signal subscribeError:^(NSError * _Nullable error) {
        NSLog(@"%@",error);
    }];

代码自上而下调用,那么3和4的发生,为什么会在2之后呢?明明3和4的代码在前呀?
为什么呢?

RACSignal,信号类,默认是冷信号,只有订阅了这个信号,才会变为热信号,值发生变化时触发。
订阅信号,调用RACSignal的subscribeNext方法。

1.创建信号,调用的方法

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}

我们注意到,该方法的参数为didSubscribe的一个block,返回值是一个RACDynamicSignal类。

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

在该方法中,didSubscribe被保存起来,此时并不会触发。

2.当信号被订阅时,在创建RACSubscriber的同时,nextBlock也被保存起来

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

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}

+ (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;
}

- (instancetype)init {
    self = [super init];

    @unsafeify(self);

    RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{
        @strongify(self);

        @synchronized (self) {
            self.next = nil;
            self.error = nil;
            self.completed = nil;
        }
    }];

    _disposable = [RACCompoundDisposable compoundDisposable];
    [_disposable addDisposable:selfDisposable];

    return self;
}

同时还创建了RACCompoundDisposable与RACPassthroughSubscriber(真正的订阅者类),同时RACDynamicSignal的didSubscribe被调用。
而在didSubscribe中,会调用[subscriber sendNext:];

#pragma mark RACSubscriber

- (void)sendNext:(id)value {
    if (self.disposable.disposed) return;

    if (RACSIGNAL_NEXT_ENABLED()) {
        RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
    }

    [self.innerSubscriber sendNext:value];
}

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

        nextBlock(value);
    }
}

sendNext底层其实就是执行subscriber的nextBlock

RACSignal可用于textFiled,button,手势,通知,KVO,定时器等。

 @weakify(self);
    //textField
    [self.accountTF.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
            @strongify(self);
        self.imgV.image = [UIImage imageNamed:x];
        if (self.imgV.image == nil) {
            self.imgV.image = [UIImage imageNamed:@"touxiang"];
        }
    }];
    
    //button
    [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            @strongify(self);
        
    }];
    
    
    //定时器
    [[RACSignal interval:1 onScheduler:[RACScheduler scheduler]] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    //手势
    UITapGestureRecognizer *tap = [UITapGestureRecognizer new];
    self.stateLabel.userInteractionEnabled = YES;
    [self.stateLabel addGestureRecognizer:tap];
    [[tap rac_gestureSignal] subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    //通知
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidChangeFrameNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    //KVO
    [RACObserve(self, name) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];

RAC中常见的相关操作:

1.信号映射:map与flattenMap
2.信号过滤:filter、ignore、 distinctUntilChanged
3.信号合并: combineLatest、reduce、merge、zipWith
4.信号连接:concat、then
5.信号操作时间:timeout、interval、dely
6.信号取值:take、takeLast、takeUntil、
7.信号跳过:skip
8.信号发送顺序:donext、cocompleted
9.获取信号中的信号:switchToLatest
10.信号错误重试:retry

简单使用举例:

//map  flattenMap
    //实际应用时 通常会跳过第一次
    [[[self.accountTF.rac_textSignal skip:1] flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        
        return [RACSignal return:[NSString stringWithFormat:@"+86-%@",value]];
        
    }]  subscribeNext:^(id  _Nullable x) {
        NSLog(@"flattenMap  ===  %@",x);
    }];
    
    
    //filter
    [[[self.passwordTF.rac_textSignal skip:1] filter:^BOOL(NSString * _Nullable value) {
        //做过滤条件
        @strongify(self);
        if (self.passwordTF.text.length > 5) {
            self.passwordTF.text = [self.passwordTF.text substringToIndex:5];
            self.stateLabel.text = @"密码长度不能大于5";
        }
        return value.length < 5;
        
    }] subscribeNext:^(NSString * _Nullable x) {
      NSLog(@"我订阅到了什么:%@",x);
        //逻辑区域
    }] ;

    
    //组合 combineLaster
    RACSignal *signalA = self.accountTF.rac_textSignal;
    RACSignal *signalB = self.passwordTF.rac_textSignal;
    
    [[RACSignal combineLatest:@[signalA, signalB] reduce:^id (NSString *account,NSString *password){
        //reduceBlock的参数个数 要与 合并的信号数组的个数保持一致
        //此处 可以将多个参数合并
        return @(account.length && password.length);
    }] subscribeNext:^(NSNumber *x) {
        self.loginBtn.backgroundColor = x.integerValue ? [UIColor greenColor] : [UIColor darkGrayColor];
        self.loginBtn.enabled = x.integerValue;
    }];
    
    //takeUntil
    //subject1 正常发送,直到subject2发送信号。意味着subject2发送信号后 subject1不能再发送信号
    //以下代码,reboot不能正常发送,除非注释掉 [subject2 sendNext:@"hani"];
    
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    
    [[subject1 takeUntil:subject2] subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"%@",x);
    }];
    
    [subject1 sendNext:@"菲尼克斯"];
    [subject1 sendNext:@"艾拇"];
    
    [subject2 sendNext:@"hani"];
    
    [subject1 sendNext:@"reboot"];

说明:

(a)flattenMap:在bind基础上封装的改变 法, 提 供的block,改变当前流,变成block返回的流对象。
(b)flatten:在flattenMap基础封装的改变 法,如果当前 反应流中的对象也是 个流的话,就可以将当前流变成当前
流中的流对象
(c)map:在flattenMap基础上封装的改变 法,在 flattenMap中的block中返回的值必须也是流对象, map则
需要,它是将流中的对象执 block后, 流的return 法 将值变成流对象。
(d)mapReplace:在map的基础上封装的改变 法,直接 替换当前流中的对象,形成 个新的对象流。
(e)filter:在Map基础上封装的改变封装,过滤掉当前流 中 符合要求的对象,将之变为空流
(f)ignore:在filter基础封装的改变 法,忽 和当 前值 样的对象,将之变为空流
(g)skip:在bind基础上封装的改变 法,忽 当前 流前n次的对象值,将之变为空流
(h)take:在bind基础上封装的改变 法,只区当 前流中的前n次对象值,之后将流变为空( 是空
流)。
(i)distinctUntilChanged:在bind基础封装的改变 法,当流中后 次的值和前 次的值 同的时候,
才会返回当前值的流,否则返回空流(第 次默认被 忽 )
(j)takeUntilBlock:在bind基础封装的改变 法,取当前 流的对象值,直到当前值满 提供的block,就会将当前流
变为空( 是空流)
(k)takeWhileBlock:在bind基础封装的改变 法,取当
前流的对象值,直到当前值 满 提供的block,就会将当 前流变为空( 是空流)
(l)skipUntilBlock:在bind基础封装的改变 法,忽 当 前流的对象值(变为空流),直到当前值满 提供的
block。
(m)skipWhileBlock:在bind基础封装的改变 法,忽 当前流的对象值(变为空流),直到当前值 满 提供的 block
(n)scanWithStart: reduceWithIndex:在bind基
础封装的改变 法, 同样的block执 每次流中的
值,并将结果 于后 次执 当中,每次都把block 执 后的值变成新的流中的对象。
(o)startWIth:在contact基础上封装的多流之间 的顺序 法,在当前流的值流出之前,加 个初
始值 (p)zip:打包多流,将多个流中的值包装成 个
RACTuple对象
(q)reduceEach:将流中的RACTuple对象进 过 滤,返回特定的衍 出的 个值对象

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