RAC-bind那些事

bind:在RAC中有着举足轻重的作用,没有它,很多功能都是没有办法实现,之前的例子中我们也了解到-eagerSequence-lazySequence 这两个方法的区别,以及flattenMapskiptaketakeUntilBlockskipUntilBlockdistinctUntilChanged等function都用到了bind方法。

我们可以发现很多地方都继承和重写bind方法。


bind.png

首先看看源码中的解释说明。RACStream.h

/// Lazily binds a block to the values in the receiver.
///
/// This should only be used if you need to terminate the bind early, or close
/// over some state. -flattenMap: is more appropriate for all other cases.
///
/// block - A block returning a RACStreamBindBlock. This block will be invoked
///         each time the bound stream is re-evaluated. This block must not be
///         nil or return nil.
///
/// Returns a new stream which represents the combined result of all lazy
/// applications of `block`.
- (instancetype)bind:(RACStreamBindBlock (^)(void))block;

RACSignal的bind

在RACSignal.m中的实现,有如下说明:

/*
 * -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:的操作中需要遵循那5点要求,以及最后一条的说明。

  1. 需要订阅原始signal。
  2. 不管原始signal何时发送数据,都会将value转换到bind的block中。
  3. 如果block返回一个signal,则订阅这个signal。
  4. block终止绑定则原始signal发送完成。
  5. 当所有的signal都完成时才会发送对subscriber发送completed。
  6. 当任何一个signal发送error时,则直接发送给订阅者。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACStreamBindBlock bindingBlock = block();

        __block volatile int32_t signalCount = 1;   // indicates self

        RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

        void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
            if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
                [subscriber sendCompleted];
                [compoundDisposable dispose];
            } else {
                [compoundDisposable removeDisposable:finishedDisposable];
            }
        };

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

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

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

            selfDisposable.disposable = bindingDisposable;
        }

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

这个函数体比较大,我们慢慢分析一下。

  1. creat了一个新的signal。所有的操作都是在这个创建的过程中。
  2. 是一个complete的block。 这里维护了一个锁(原子操作的互斥锁)。
  3. 是一个addSignal的block。在block内部会订阅参数传进来的signal。
  4. 在这个@autoreleasepool中,会订阅原始的signal。在订阅的过程中,如果bindingBlock返回了一个signal,进入(3)的addSignal的block。如果bindingBlock要stop绑定,则进入(2)complete的Block。

例子就不说了,我们经常使用的mapflatMap调用的就是bind

以上是RACSignalbind操作。

RACSequence的bind

- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
    RACStreamBindBlock bindBlock = block();
    return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
}

blockcopy一份存储在bindBlock中,然后调用另一个方法。

在这个方法中,我们又看到了一段注释。

// Store values calculated in the dependency here instead, avoiding any kind
// of temporary collection and boxing.
//
// This relies on the implementation of RACDynamicSequence synchronizing
// access to its head, tail, and dependency, and we're only doing it because
// we really need the performance.

就是为了告诉我们将数据存储起来避免任何形式的临时处理,在以后的依赖关系处理中调用。 依赖RACDynamicSequence来同步处理相关head tail。这样做的目的是为了提高性能。

- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
    
    __block RACSequence *valuesSeq = self;
    __block RACSequence *current = passthroughSequence;
    __block BOOL stop = NO;

    RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
        while (current.head == nil) {
            if (stop) return nil;

            // We've exhausted the current sequence, create a sequence from the
            // next value.
            id value = valuesSeq.head;

            if (value == nil) {
                // We've exhausted all the sequences.
                stop = YES;
                return nil;
            }

            current = (id)bindBlock(value, &stop);
            if (current == nil) {
                stop = YES;
                return nil;
            }

            valuesSeq = valuesSeq.tail;
        }

        NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current);
        return nil;
    } headBlock:^(id _) {
        return current.head;
    } tailBlock:^ id (id _) {
        if (stop) return nil;

        return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail];
    }];

    sequence.name = self.name;
    return sequence;
}

这里重点用到了一个RACDynamicSequence的一个方法。

// RACDynamicSequence.m

+ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock {
    NSCParameterAssert(dependencyBlock != nil);
    NSCParameterAssert(headBlock != nil);

    RACDynamicSequence *seq = [[RACDynamicSequence alloc] init];
    seq.headBlock = [headBlock copy];
    seq.tailBlock = [tailBlock copy];
    seq.dependencyBlock = [dependencyBlock copy];
    seq.hasDependency = YES;
    return seq;
}

这个方法中,主要是返回一个RACDynamicSequence类型的数据,并将各种block存储起来。

接着上面的bind来说。在+sequenceWithLazyDependency的函数内部,只是将数据copy了一份存储起来,并没有做任何处理,在函数的调用时,并没有使用到block,所以数据是不会做处理的。只能在接下来的某些函数上会依赖这些存储的数据,才会用到这里的相关内容。比较绕,举个例子。

NSArray *array1 = @[@"a", @"b", @"c"];

RACSequence *seq1 = [array1.rac_sequence map:^id(id value) {
    NSLog(@"lazy value = %@", value);
    return value;
}];
    
// NSArray *array3 = seq1.array;

关于eagerSequencelazySequence的关系,可以看之前的章节(RACSignal、RACSequence、RACTuple也有例子说明)。

由于最后一行被注释掉了,所以控制台不会输出任何内容。当使用了seq1.array的时候,才会触发[seq1 map:]

当获取调用栈的时候,发现,在调用array方法之后,由于重写了NSFastEnumeration协议中的方法,才会调用相关的方法

lazy-array的调用栈.png
#pragma mark NSFastEnumeration

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
    if (state->state == ULONG_MAX) {
        // Enumeration has completed.
        return 0;
    }

    // We need to traverse the sequence itself on repeated calls to this
    // method, so use the 'state' field to track the current head.
    RACSequence *(^getSequence)(void) = ^{
        return (__bridge RACSequence *)(void *)state->state;
    };

    void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) {
        // Release the old sequence and retain the new one.
        CFBridgingRelease((void *)state->state);

        state->state = (unsigned long)CFBridgingRetain(sequence);
    };

    void (^complete)(void) = ^{
        // Release any stored sequence.
        setSequence(nil);
        state->state = ULONG_MAX;
    };

    if (state->state == 0) {
        // Since a sequence doesn't mutate, this just needs to be set to
        // something non-NULL.
        state->mutationsPtr = state->extra;

        setSequence(self);
    }

    state->itemsPtr = stackbuf;

    NSUInteger enumeratedCount = 0;
    while (enumeratedCount < len) {
        RACSequence *seq = getSequence();

        // Because the objects in a sequence may be generated lazily, we want to
        // prevent them from being released until the enumerator's used them.
        __autoreleasing id obj = seq.head;
        if (obj == nil) {
            complete();
            break;
        }

        stackbuf[enumeratedCount++] = obj;

        if (seq.tail == nil) {
            complete();
            break;
        }

        setSequence(seq.tail);
    }

    return enumeratedCount;
}

大致的分析一下:

  1. 由于我们在使用seq1.array时,调用了NSFastEnumeration的方法;
  2. 其实现内部使用了seq.head
#pragma mark RACSequence

- (id)head {
    @synchronized (self) {
        id untypedHeadBlock = self.headBlock;
        if (untypedHeadBlock == nil) return _head;

        if (self.hasDependency) {
            if (self.dependencyBlock != nil) {
                _dependency = self.dependencyBlock();
                self.dependencyBlock = nil;
            }

            id (^headBlock)(id) = untypedHeadBlock;
            _head = headBlock(_dependency);
        } else {
            id (^headBlock)(void) = untypedHeadBlock;
            _head = headBlock();
        }

        self.headBlock = nil;
        return _head;
    }
}

3.在head的方法中调用了self.dependencyBlock()。这个block是之前RACDynamicSequence存储的一个block。

4.这时会使用map^(){}中的的内容。

以上是lazy的调用过程分析。接下来看看eager的map分析

RACEagerSequence的bind

因为在eagerSequence的方法中,返回的是一个RACEagerSequence类型的数据。

// RACSequence.m

- (RACSequence *)eagerSequence {
    return [RACEagerSequence sequenceWithArray:self.array offset:0];
}
// RACArraySequence.m(RACEagerSequence继承RACArraySequence)

+ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
    NSCParameterAssert(offset <= array.count);

    if (offset == array.count) return self.empty;

    RACArraySequence *seq = [[self alloc] init];
    seq->_backingArray = [array copy];
    seq->_offset = offset;
    return seq;
}

这里存储了backingArray。下面的bind操作会用到。

接下来,我们直接看看bind操作

- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != nil);
    RACStreamBindBlock bindBlock = block();
    NSArray *currentArray = self.array;
    NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count];
    
    for (id value in currentArray) {
        BOOL stop = NO;
        RACSequence *boundValue = (id)bindBlock(value, &stop);
        if (boundValue == nil) break;

        for (id x in boundValue) {
            [resultArray addObject:x];
        }

        if (stop) break;
    }
    
    return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name];
}

我们具体的来分析一下:

  1. 首先将block存储起来
  2. 调用了array的方法,这里调用的array跟RACSequence调用的完全不一样。
- (NSArray *)array {
      return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}
  1. 直接便利当前的数组,同时获取bindBlock()

<b>举个例子:</b>

NSArray *array2 = @[@1, @2, @3];

RACSequence *seq2 = [array2.rac_sequence.eagerSequence map:^id(id value) {
    NSLog(@"eager value = %@", value);
    return value;
}];

这里即使不使用seq2.array也会直接输出NSLog中的内容。
当前的调用栈,如下图:

eager-array的调用栈.png

总结

以上时bind的相关内容,signal的bind只有一种。不用做区分。而sequence的bind最常用的有两种:一种是RACSequence使用RACDynamicSequence来存储value的懒加载模式;一种是RACEagerSequence直接调用模式。

上述方法中用到的是map函数,在map内部调用的是flatMapflatMap内部调用的bind

flatMapmap的区别:

  1. map的返回值是一个value。
  2. flatMap返回值是一个RACStream

写的不好,欢迎各位大神指正。喜欢的点赞,加个关注,谢谢!

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

推荐阅读更多精彩内容

  • RAC使用测试Demo下载:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees阅读 6,353评论 3 10
  • 前言由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目...
    Karos_凯阅读 1,725评论 0 6
  • (大喇叭:)本篇比较长请耐心阅读,主要解释一下RACSignal的部分内容,详细介绍了RACSequence和RA...
    不会飞的小白阅读 1,699评论 0 2
  • 我一直觉得,RAC是仿佛已经被遗忘的话题,擅长的人已经把它化为内力,不擅长的早已忘记这个技术的存在,这个暂且按住不...
    daixunry阅读 4,345评论 0 11
  • 中国茶,源流长, 发神农,起汉唐。 先入药,后成汤,闻四海,传八方。 茶经书,陆羽撰,人工培,吴理真。 产茶区,布...
    Y啵啵哒阅读 489评论 0 0