ReactiveCocoa核心方法bind

ReactiveCocoa操作
所有的信号(RACSignal)都可以进行操作处理,因为所有的操作方法都定义在RACStream.h中,而RACSignal继承RACStream。
ReactiveCocoa操作思想
运用的是(钩子)HooK思想,Hook是一种用于改变API执行结果的技术。
Hook用处:截获API调用的技术。
Hook原理:在每次调用一个API返回结果之前,先执行自己的方法,改变方法的输出。
RAC开发方式:RAC核心开发方式也就是绑定,之前的开发方式是赋值,而RAC开发应该把重心放在绑定,也就是在创建一个对象的时候就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
例如:把数据展示在控件上,之前都是重写控件的setModel方法,用RAC就可以在一开始创建控件的时候就绑定好数据

ReactiveCocoa核心方法bind
ReactiveCocoa操作的核心方法是bind(绑定),给RAC中的信号进行绑定,只要信号一发送数据就能监听到,从而把发送数据改成自己想要的数据。
在开发中很少使用bind方法,bind属于RAC中的底层方法,RAC已经封装了很多好用的其他方法,底层都是调用bind,用法比bind简单。
bind方法简单介绍和使用:
// 创建信号

  1. RACSubject *subject = [RACSubject subject];
    // 绑定信号,只有绑定的信号被订阅就会被调用
    // bind 返回一个绑定信号
  2. RACSignal *bindsignal = [subject bind:^RACStreamBindBlock{
    return ^RACSignal *(id value, BOOL *stop){
        // block调用:只要原信号发送数据,就会调用block
        // block作用:处理原信号内容
        // value:原信号发送的内容
        
        NSLog(@"接收到原信号的内容%@",value);
        // 返回信号,不能传nil,返回空信号[RACSignal empty]
        value = @"程倩";
        return [RACReturnSignal return:value];
    };
    
}];
  1. [bindsignal subscribeNext:^(id x) {
    NSLog(@"接收到绑定信号处理完的内容%@",x);
    }];
    // 原型号发送数据
  2. [subject sendNext:@"123"];
    输出:
    2016-05-17 11:41:00.549 ReactiveCocoaTest1[1102:67222] 接收到原信号的内容123
    2016-05-17 11:41:00.549 ReactiveCocoaTest1[1102:67222] 接收到绑定信号处理完的内容程倩

分析

  1. RACSubject *subject = [RACSubject subject];
    

    创建一个RACSubject原信号 初始化一个数组_subscribers
    源码:
    @implementation RACSubject

      #pragma mark Lifecycle
    
    • (instancetype)subject {
      return [[self alloc] init];
      }
    • (id)init {
      self = [super init];
      if (self == nil) return nil;
      //初始化一个订阅者和_subscribers数组
      _disposable = [RACCompoundDisposable compoundDisposable];
      _subscribers = [[NSMutableArray alloc] initWithCapacity:1];

      return self;
      }

  2. RACSignal *bindsignal = [subject bind:^RACStreamBindBlock{
    return ^RACSignal (id value, BOOL stop){
    NSLog(@"接收到原信号的内容%@",value);
    // 返回信号,不能传nil,返回空信号[RACSignal empty]
    value = @"程倩";
    return [RACReturnSignal return:value];
    };
    }];
    bind:这个函数存在于RACSubject的父类RACSignal中
    源码:
    /

  • @param block 传入的block
  • @return 返回绑定后的信号
    */
  • (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);// 一个异常处理,不太懂
 // 直接返回了一个绑定后的信号 创建一个新的信号返回,创建的是(RACDynamicSignal类型信号)
// RACDynamicSignal是RACSignal的子类 将这个didSubscribe block块保存在了RACDynamicSignal中的_didSubscribe block中

{
可以看到 [RACSignal createSignal:]中返回的结果是[RACDynamicSignal createSignal:didSubscribe];
看一下[RACDynamicSignal createSignal:didSubscribe]中的源码:
@implementation RACDynamicSignal

  • (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
    }
    返回的是RACDynamicSignal对象,并且将传入的block块保存在了对象的_didSubscribe中
    }

// 所以说这个block现在并没有被执行,只是将这个block存在了绑定信号中了
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
//当绑定的信号被订阅的时候开始执行这个block
//调用block后得到创建绑定信号时传入block返回的block 这个block返回一个新的信号
RACStreamBindBlock bindingBlock = block();

    NSMutableArray *signals = [NSMutableArray arrayWithObject:self];

    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

           // 这个block暂时不会被执行
    void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
        BOOL removeDisposable = NO;

        @synchronized (signals) {
            [signals removeObject:signal];

            if (signals.count == 0) {
                [subscriber sendCompleted];
                [compoundDisposable dispose];
            } else {
                removeDisposable = YES;
            }
        }

        if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
    };

     // 这个block暂时不会被执行
    void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
        @synchronized (signals) {
            [signals addObject:signal];
        }

        RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
        [compoundDisposable addDisposable:selfDisposable];
       // 在这里对返回的RACReturnSignal内容处理完成的信号进行订阅
        RACDisposable *disposable = [signal subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [compoundDisposable dispose];
            [subscriber sendError:error];
        } completed:^{
            @autoreleasepool {
                completeSignal(signal, selfDisposable);
            }
        }];

        selfDisposable.disposable = disposable;
    };

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

               //当代码执行到这里的时候
      // 在这里对原信号进行订阅,当原信号发送数据的时候会调用这个Nextblock
        RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
            
            if (compoundDisposable.disposed) return;

            BOOL stop = NO;

// 当原信号发送数据的时候在这里调用了之前绑定block返回的block内容,得到一个信号
id signal = bindingBlock(x, &stop);

            @autoreleasepool {

// 这这里执行了addSignal block,传入绑定block执行完毕返回的信号
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];

        selfDisposable.disposable = bindingDisposable;
    }

    return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];

}

3.订阅绑定信号:

[bindsignal subscribeNext:^(id x) {
NSLog(@"接收到绑定信号处理完的内容%@",x);
}];
分析:
subscribeNext:函数源码
bindsignal的类型是RACDynamicSignal类型

  • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
    }
    [self subscribe:o]的源码是:

  • (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    // 这个函数里面记录了绑定信号的订阅者
    源码{
    - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
    NSCParameterAssert(subscriber != nil);

    self = [super init];
    if (self == nil) return nil;

    // 记录下了绑定信号的订阅者
    _innerSubscriber = subscriber;
    _signal = signal;
    _disposable = disposable;

    [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
    return self;
    }

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

    }
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
    if (self.didSubscribe != NULL) {
    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
    // 在这里可以看到订阅绑定信号的时候调用了创建绑定信号时候传入的block 跳转到2步骤中的 block
    RACDisposable *innerDisposable = self.didSubscribe(subscriber);
    [disposable addDisposable:innerDisposable];
    }];
    [disposable addDisposable:schedulingDisposable];
    }
    return disposable;
    }

  1. // 原型号发送数据
    [subject sendNext:@"123"];
    分析:
    拿到数组中的所有的block调用,(原信号被订阅时候存下的block)

例子:
假设想监听文本框的内容,并且在每次输出结果的时候,都在文本框的内容拼接一段文字“输出:”

  // 方式一:在返回结果后,拼接。
[_textfield.rac_textSignal subscribeNext:^(id x) {
    NSLog(@"输出:%@",x);
}];

// 方式二:在返回结果前,拼接,使用RAC中bind方法做处理。
// bind方法参数:需要传入一个返回值是RACStreamBindBlock的block参数
// RACStreamBindBlock是一个block的类型,返回值是信号,参数(value,stop),因此参数的block返回值也是一个block。

// RACStreamBindBlock:
// 参数一(value):表示接收到信号的原始值,还没做处理
// 参数二(*stop):用来控制绑定Block,如果*stop = yes,那么就会结束绑定。
// 返回值:信号,做好处理,在通过这个信号返回出去,一般使用RACReturnSignal,需要手动导入头文件RACReturnSignal.h。

// bind方法使用步骤:
// 1.传入一个返回值RACStreamBindBlock的block。
// 2.描述一个RACStreamBindBlock类型的bindBlock作为block的返回值。
// 3.描述一个返回结果的信号,作为bindBlock的返回值。
// 注意:在bindBlock中做信号结果的处理。

// 底层实现:
// 1.源信号调用bind,会重新创建一个绑定信号。
// 2.当绑定信号被订阅,就会调用绑定信号中的didSubscribe,生成一个bindingBlock。
// 3.当源信号有内容发出,就会把内容传递到bindingBlock处理,调用bindingBlock(value,stop)
// 4.调用bindingBlock(value,stop),会返回一个内容处理完成的信号(RACReturnSignal)。
// 5.订阅RACReturnSignal,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。

// 注意:不同订阅者,保存不同的nextBlock,看源码的时候,一定要看清楚订阅者是哪个。
// 这里需要手动导入#import <ReactiveCocoa/RACReturnSignal.h>,才能使用RACReturnSignal。

[[_textfield.rac_textSignal bind:^RACStreamBindBlock{
    // 什么时候调用:
    // block作用:表示绑定了一个信号.
    return ^RACStream *(id value, BOOL *stop){
        // 什么时候调用block:当信号有新的值发出,就会来到这个block。
        // block作用:做返回值的处理
        // 做好处理,通过信号返回出去.
        return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
    };
}] subscribeNext:^(id x) {
    NSLog(@"%@",x);

}];

不正确之处,欢迎补充
测试代码 https://github.com/CharType/ReactiveCocoaTest

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

推荐阅读更多精彩内容