ReactiveCocoa核心方法bind的底层实现

(把以前 Hexo 博客中的内容都迁移过来了)

bind 是 ReactiveCocoa的核心方法, 顾名思义:绑定, 跟以往的赋值不同.
RAC 封装了很多方法, 底层都是用的 bind, 平常 bind 方法基本不用, 但是理解原理后其它方法很容易理解了, 比如: map, flattenMap等.

文章目录

  1. 使用 bind 方法
  2. bind 的实现原理
  3. 总结

使用 bind 方法

typedef RACSignal * _Nullable (^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop);
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block;

上面是 bind 方法的声明, bind 方法只接收一个返回值是RACSignalBindBlock类型的无参 block. RACSignalBindBlock也是一个 block, 携带两个参数value和 stop, 返回值是 RACSignal类型的信号.
下面代码实现一下:

- (void)testBind {
    // 创建源信号
    RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        [subscriber sendNext:@"hello, "];
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"源信号被销毁, 这里进行收尾工作...");
        }];
        
    }];
    
    // 进行bind, 返回绑定信号
    RACSignal *bindSignal = [sourceSignal bind:^RACSignalBindBlock _Nonnull{
        
        RACSignalBindBlock bindBlock = ^RACSignal*(id value, BOOL *stop) {
            RACSignal *returnSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                
                // 修改值
                NSString *newValue = [NSString stringWithFormat:@"%@%@", value, @"ReacticeCocoa bind"];
                
                [subscriber sendNext:newValue];
                
                return [RACDisposable disposableWithBlock:^{
                    NSLog(@"返回信号被销毁, 这里进行收尾工作...");
                }];
                
            }];
            
            return returnSignal;
        };
        
        return bindBlock;
    }];
    
    
    // 订阅绑定信号
    [bindSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"接收到信号传送的值: %@", x);
    } completed:^{
        NSLog(@"信号发送完毕");
    }];
}

console输出:

2016-05-01 18:18:20.874 ReactiveCocoaDemo[74310:880621] 接收到信号传送的值: hello, ReacticeCocoa bind
2016-05-01 18:18:20.875 ReactiveCocoaDemo[74310:880621] 返回信号被销毁, 这里进行收尾工作...
2016-05-01 18:18:20.875 ReactiveCocoaDemo[74310:880621] 源信号被销毁, 这里进行收尾工作...

从上面的结果可以看出,原本发送的”hello,”字符串,在 bind 方法中获取到发送的值, 进行修改重新发送一个新值 “hello,ReactiveCocoa bind”, 最后订阅 bindSignal,得到修改后的值.
bind是 ReactiveCocoa信号操作的核心,能够拿到上次信号发送的值,进行操作,传递给下一个信号发送,从基本的 map 方法就能体会.

使用 bind的时候会涉及到三个信号:

暂且命名为: 源信号(sourceSignal), 绑定信号(bindSignal), 返回信号(returnSignal)

那么这个三个信号有什么作用呢?
源信号(signal): 提供数据源的信号, 通过这个信号的订阅者发送原始数据.
绑定信号(sourceSignal): 通过订阅它来接收处理后的数据.
返回信号(returnSignal): 很明显要想形成一个完整的流程缺少一个”桥梁”, 缺少发送处理后的数据的信号, 即通过返回信号能够发送处理后的数据.

下面会讲解三者是怎么配合的.

bind 的实现原理

进入 bind 方法的实现可以非常清晰的了解到 bind 的使用以及原理:

-bind: should:

  1. Subscribe to the original signal of values.
  2. Any time the original signal sends a value, transform it using the binding block.
  3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they’re received.
  4. If the binding block asks the bind to terminate, complete the original signal.
  5. When all signals complete, send completed to the subscriber.
    If any signal sends an error at any point, send that to the subscriber.

创建绑定信号

调用 bind 方法会新创建一个 RACDynamicSignal信号, 这个信号就是绑定信号(bindSignal), 我们订阅绑定信号(bindSignal)就会执行绑定信号的 didSubscribe block.

- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        
        // ......
        
    }] setNameWithFormat:@"[%@] -bind:", self.name];
}

按照调用步骤:

订阅绑定信号(bindSignal)

// 订阅绑定信号
[bindSignal subscribeNext:^(id  _Nullable x) {
    NSLog(@"接收到信号传送的值: %@", x);
}];

执行绑定信号(bindSignal)的didSubscribe block:

RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
        // Manually check disposal to handle synchronous errors.
        if (compoundDisposable.disposed) return;
        
        BOOL stop = NO;
        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);
        }
    }];

注意self调用了 subscribeNext 方法, 这个 self 是 源信号(sourceSignal), 也就说, 在订阅绑定信号的时候会订阅源信号,此时就是调用原信号的 didSubscribe block 发送数据:

[subscriber sendNext:@"hello, "];

此时 x 的值为 “hello, “.

调用 bindBlock, 得到返回信号(returnSignal)

id signal = bindingBlock(x, &stop);

3.调用 addSignal block:

if (signal != nil) 
    addSignal(signal);

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

在这个 block 中会订阅返回信号,那么就会调用返回信号的 didSubscribe block, 在这个 block中对原始数据进行处理, 然后发送处理后的数据, 也就是:

// 修改值
NSString *newValue = [NSString stringWithFormat:@"%@%@", value, @"ReacticeCocoa bind"];[subscriber sendNext:newValue];
                
return [RACDisposable disposableWithBlock:^{
           NSLog(@"返回信号被销毁, 这里进行收尾工作...");
       }];

发送处理后数据

那么此时在 addSignal block中调用:

[subscriber sendNext:x];

此时 x 的值就是经过返回信号处理后的数据,即”hello,ReactiveCocoa bind”. 注意此处的subscriber是绑定信号的订阅者, 使用绑定信号订阅者来发送数据,那么我们外面订阅绑定信号的地方就能收到数据:

// 订阅绑定信号
[bindSignal subscribeNext:^(id  _Nullable x) {
    NSLog(@"接收到信号传送的值: %@", x);
}];

至此一个完成的发送数据流程走完, 源信号发送原始数据, 通过bind 方法传入返回信号,利用返回信号来处理原始数据,发送处理后的新数据, 在 bind 内部订阅返回信号,收到处理后的新数据, 使用绑定信号的订阅者来发送新数据, 那么我们在外面订阅绑定信号就能收到处理后的新数据.

总结一下

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

推荐阅读更多精彩内容