ReactiveCocoa的bind方法

RAC的Bind方法

RAC提供了一堆可以提高开发效率的方法,比如filter,map,flattenMap等值处理方法,几乎每个方法点到底,都能看到一个叫做bind的方法.这个方法就是RAC相对底层的方法.弄明白它,对于理解RAC是非常有帮助的.

现象

- (void)runTest {
    // 1.
    RACSubject *subject = [RACSubject subject];
    // 2.
    RACSignal *bindSignal = [subject bind:^RACSignalBindBlock _Nonnull {
       
        return ^RACSignal *( id value, BOOL *stop ) {//这个block只要源信号发送数据,就会调用
            NSLog(@"接收到源信号的内容: %@",value);
            //2.1
            //返回信号,不能传nil,如果真的想返回空信号,用empty(其实就是一个alloc
            // init) . 如果返回empty,那就订阅链就断了.
           //  return [RACSignal empty];
            return [RACReturnSignal return:value];
        };

    }];

    // 3.
    [bindSignal subscribeNext:^( id _Nullable x ){
            //3.1
        NSLog(@"订阅的地方,也就是处理完的信号:%@",x);
    }];
    // 4.
    [subject sendNext:@"123"];
}

1.创建源信号
2.通过bind得到绑定信号
任何信号都能调用 bind 方法,bind方法需要一个RACSignalBindBlock类型的参数,这个类型定义typedef RACSignal * _Nullable (^RACSignalBindBlock)(id _Nullable value, BOOL *stop), 早期版本,返回值是 RACStream,现在是 RACSignal,其实都一样.RACSignal继承RACStream. 响应式编程中,万物皆是流
3.订阅绑定信号
3.1 如果2.1处返回的是 empty, 那么3.1处将不会执行.
4.源信号发送数据

从源码看

  1. 创建信号 RACSubject *subject = [RACSubject subject];
    RACSubject.m文件中
    - (instancetype)init {
    self = [super init];
    if (self == nil) return nil;

    _disposable = [RACCompoundDisposable compoundDisposable];
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];
    
    return self;
    }

_subscribers 这个是订阅者数组.

  1. 绑定信号 [subject bind:^RACSignalBindBlock _Nonnull{....}
    这个bind点进去,里面代码如下:

- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);
//没有 block() ,即没有执行参数block
//2.1
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
//2.2
        RACSignalBindBlock bindingBlock = block();
        ...

可以看到,在bind方法中,参数block到结束的return都没有去调用.bind返回了一个新创建的信号.这个在代码中,体现为RACSignal *bindSignal .
而在 return后面的 [[RACSignal createSignal:^(id<RACSubscriber> subscriber),这个createSignal定义如下:

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

可以看出,这个 createSignal:需要一个didSubscribe block,也就是在2.1处,传递的:

^(id<RACSubscriber> subscriber) {
        RACSignalBindBlock bindingBlock = block();
        ...

继续看下去,[RACDynamicSignal createSignal:didSubscribe];这句中,createSignal的实现如下:

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

就是说,创建了一个 RACDynamicSignal类型的对象,然后将didSubscribe block赋值给它的_didSubscribe属性. 属性定义如下:

@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);

到此,这个bind流程走完了.

总结就是:
bind方法传递 RACSignalBindBlock 类型的参数到了 createSignal 的参数block中, createSingal 的参数block被保存为 RACDynamicSignal 的didSubscribe属性.

注意,到此为止,没有任何block获得执行.也就是如果代码单纯如下:

    // 1.
    RACSubject *subject = [RACSubject subject];
    // 2.
    RACSignal *bindSignal = [subject bind:^RACSignalBindBlock _Nonnull {
    //breakpoint_1
        return ^RACSignal *( id value, BOOL *stop ) {
            NSLog(@"接收到源信号的内容: %@",value);
  //breakpoint_2
            return [RACReturnSignal return:value];
        };

    }];

打在两个block中的两个breakpoint都不会执行.

  1. 订阅绑定信号
 [bindSignal subscribeNext:^( id _Nullable x ){
        NSLog(@"订阅的地方,也就是处理完的信号:%@",x);
    }];

订阅信号,这个subscribeNext方法如下:

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

可以看到,方法内部创建了一个RACSubscriber订阅者.这个创建方法subscriberWithNext定义如下:

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

可以看到,就是将对应的 next ,error,complete三种类型的block保存到订阅者中.在这里,我们的nextBlock就是:

^( id _Nullable x ){
        NSLog(@"订阅的地方,也就是处理完的信号:%@",x);
    }];

也就是说,将我们的以上block保存到订阅者中.

在3.1之后, 3.2 [self subscribe:o] 这是真正的去订阅信号.订阅信号,会触发信号的didSubscribe.也就是代码2.2所在的block会获得执行,而根据刚刚的线路.代码2.2中有RACSignalBindBlock bindingBlock = block();,也就是我们传递的返回值是RACSignalBindBlock类型的block会执行.也就是 breakPoint_1会执行 ,而代码中 block()的返回值是RACSignalBindBlock,代码将其保存到了bindingBlock这个变量中.
在bind方法末尾:

    @autoreleasepool {
            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];
            //3.3
            RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
                // Manually check disposal to handle synchronous errors.
                if (compoundDisposable.disposed) return;

                BOOL stop = NO;
                //3.4
                id signal = bindingBlock(x, &stop);

                @autoreleasepool {
                    if (signal != nil) addSignal(signal);
                    if (signal == nil || stop) {
                        [selfDisposable dispose];
                        completeSignal(selfDisposable);
                    }
                }
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = bindingDisposable;
        }
        ```

3.3 `RACDisposable *bindingDisposable = [self subscribeNext:^(id x)  ` self 代指的调用者,也就是我们的源信号.订阅这个信号,也是会创建一个订阅者.也是用来保存block的.以上代码中,nextBlock , errorBlock, completeBlock都有,都会被保存.
而nextBlock做了:


```objc

        id signal = bindingBlock(x, &stop);

        @autoreleasepool {
        //4.5
            if (signal != nil) addSignal(signal);
            if (signal == nil || stop) {
                [selfDisposable dispose];
                completeSignal(selfDisposable);
            }
        }

这样订阅流程就走完了.

4.源信号发送数据 [subject sendNext:@"123"];
这样会执行源信号订阅者的nextBlock,而nextBlock中有 bindingBlock的执行.bindingBlock有个x参数,这就是我们send的 @"123".
注意4.5这里有个判空的过程:
if (signal != nil) addSignal(signal);
如果在2.2我们返回了空信号,那么addSignal(signal)是不会得到执行的.
然后4.5这个bingBlock的返回值作为参数,传递到了addSignal中,addSignal的定义如下:

void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
            OSAtomicIncrement32Barrier(&signalCount);

            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];

            RACDisposable *disposable = [signal subscribeNext:^(id x) {
                [subscriber sendNext:x];
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = disposable;
        };

内部,订阅了我们的处理后的信号(我们开始代码的bindSignal).这样新信号的didSubscribe block 就会得到执行.

这就是使用bind进行了一次信号的完整流程.

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

推荐阅读更多精彩内容