使用RAC惯用法构建队列

时间:2014-04-30 08:01:39

标签: objective-c reactive-cocoa

我一直试图想出一个在RAC中创建队列的解决方案,但到目前为止还未能找到一个可行的构造。我需要简单的队列功能。有一个生产者,无论读者如何都提供输入,只要没有消费者,队列就会存储所有输入。队列应该保留在没有订户可用时发送的所有消息,并在订阅发生这样排空队列时逐个发送这些消息。

我能解决这个问题的理论方法是使用一个中间存储器,一个可变数组,其中存储输入并从输出产生位置。但这远不是惯用的解决方案。

也许可以使用replay:设置某些内容。这只需要在没有订阅者的情况下激活重放功能。

所以问题是:有没有办法用RAC惯用法实现队列?

3 个答案:

答案 0 :(得分:1)

有一些问题解决方案ReactiveCocoa并不直接适用。我直接说",因为我发现通常有很多机会在解决方案附近使用RAC (通常是通过包装它),即使它没有&# 39;在解决方案中使用RAC 是有意义的。您的情况与RAC最佳实践有些不一致,因为它涉及提供来自共享可变状态的值。

当您能够根据"冷信号"来思考您的问题时,RAC效果最佳。冷信号是导致生成唯一发送给该订户的值的信号。这与热信号"形成鲜明对比。热信号是订阅实际上不会导致生成值的信号;假定无论是否有订户都生成这些值,并且将值传递给所有订户。热信号引入问题的一种方式是它们可以暴露竞争条件和关于将单个值传递给不同订户的时间的问题。即使只有一个用户,当负责发送值的代码位于信号订阅的管辖范围之外时,信号的大部分灵活性也会被删除。

例如,许多信号操作会对价值交付的时间产生一些影响。如果发送给用户的值对于该用户是唯一的(如冷信号的情况),那么竞争条件是不可能的,因为竞赛中只有一个参赛者(用户)。这是如何有用的一个例子是使用RACSchedulers:RAC使得在不编写显式同步的情况下可以轻松使用多个线程,因为这些值只对一个订阅者可见。

另一方面,如果多个订阅者收到共享"全球"值(因为这些值不是每个订阅者唯一的,而是来自某些共享源),很难推断或同步这些值到订阅者的传递。事件发生在时间" A",但是一个订阅者在时间" B"而另一个订户在时间" C"看到它们。即使两个订户都使用相同的信号操作,也会发生这种情况。虽然在某些情况下这可能是可以接受的,但您会惊讶于这会导致代码的正确性(或推理)出现问题。

您描述的队列是后者的实例。如果您实际上完全使用RAC构建它,您最终会得到某种形式的热信号,您通常希望避免这种信号。 这并不意味着您无法使用ReactiveCocoa 。这意味着如果你想使用ReactiveCocoa,你应该尝试在这个问题中找到冷信号。 RAC框架本身有许多与共享可变状态一起工作的例子;一个例子是RACObserve():任何地方的任何代码都可以在被观察对象上调用-setFoo:,而从RACObserve()返回的信号将发送该值。这是共享的可变状态。但是如果你仔细看看RACObserve()实际上是如何工作的,你会发现它会产生一个冷信号,因为每个订阅都有一个独特的效果:它会在该关键路径上为观察对象添加一个新的观察者。确实,在这种情况下,传递给一个订阅者的值可能与传递给另一个订阅者的值相同,但是它理解订阅是分开的,并且添加了唯一的键/值观察者被观察的对象。

您可以执行相同的操作来实现队列。考虑设计没有任何RAC概念的队列"内置",然后根据需要使用RAC,通过在创建和处置订阅时添加和删除使用者来更容易使用队列。例如:

@interface MyQueue
+ (instancetype)queueWithProducer:(id<MyProducer>)producer;
- (void)addConsumer:(id<MyQueueConsumer>)queueConsumer;
- (void)removeConsumer:(id<MyQueueConsumer>)queueConsumer;
@end

(据推测,你会有一些逻辑将队列中的值分配给多个消费者,但这超出了这个问题的主题。)

一旦你有一个符合你的规范的工作队列,你就可以想办法使用ReactiveCocoa来更轻松地使用它。遵循RACObserve()的先例,您可以添加一个返回信号的类别方法,该方法为每个订阅者添加该信号的使用者(您也可以直接将其添加到MyQueue界面而不是使用类别):< / p>

@interface MyQueue (SignalSupport)
- (RACSignal *)consumerSignal;
+ (id<MyQueueConsumer>)consumerForSubscriber:(id<RACSubscriber>)subscriber;
@end

@implementation MyQueue (SignalSupport)
- (RACSignal *)consumerSignal
{
    @weakify(self);
    return [RACSignal createSignal:^RACDisposable * (id<RACSubscriber> subscriber) {
        @strongify(self);
        id<MyQueueConsumer> consumer = [MyQueue consumerForSubscriber:subscriber];
        [self addConsumer:consumer];

        return [RACDisposable disposableWithBlock:^() {
            @strongify(self);
            [self removeConsumer:consumer];
        }];
    }];
}

+ (id<MyQueueConsumer>)consumerForSubscriber:(id<RACSubscriber>)subscriber
{
    // Assuming you had already created a `MyBlockConsumerImpl` class:
    MyBlockConsumerImpl *impl = [[MyBlockConsumerImpl alloc] initWithConsumeBlock:^(id value) {
        [subscriber sendNext:value];
    }];
    return impl;
}
@end

您可以看到ReactiveCocoa实际上并未用于实现队列本身,但队列使用者(通过信号订阅)仍然可以从信号合成中受益。

我一直在尝试完全从信号操作构建复杂而有状态的系统。它可以完成,但您最终可能会遇到难以理解和维护的代码。您可能有一天会用一个简单的实现来替换它,并找到一种适当的方法来补充它与ReactiveCocoa。使用ReactiveCocoa找到完全消除状态的方法总是更好,但有时候你无法避免它,因为这是Objective-C,并且可可是一个有状态的环境。在这些情况下,您通常仍然可以找到从ReactiveCocoa中受益的方法,但不会被带走。

答案 1 :(得分:0)

RACCommand似乎符合您的要求

RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSString *value = [NSString stringWithFormat:@"value:%@", input];
        [subscriber sendNext:value];
        return nil;
    }];
}];

command.allowsConcurrentExecution = YES;

[[command.executionSignals switchToLatest] subscribeNext:^(id x) {
    NSLog(@"got %@", x);
}];

for (int i = 0; i < 10; i++) {
    [command execute:@(i)];
}

----更新----

使用replayLazily

__block id<RACSubscriber> producer;

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    producer = subscriber;
    return nil;
}] replayLazily];

// to trigger signal creation manually
[signal subscribeNext:^(id x) {}];

[producer sendNext:@"foo"];
[producer sendNext:@"bar"];

[signal subscribeNext:^(id x) {
    NSLog(@"x:%@", x);
}];

答案 2 :(得分:0)

@limboy给了我RACCommand序列化RACSignals的方向。

但是RACCommand并没有真正提供任何序列化,所以我做了 RACSerialCommand序列化命令执行。

随意尝试。