添加和删​​除RACSignal携带的阵列

时间:2014-05-13 08:38:18

标签: objective-c reactive-cocoa

我有一个RACSignal,它携带了Bookmark

类对象的NSArray
RACSignal *bookmarksSignal = ... // Carries @[[[Bookmark alloc] init], ...]

两个RACCommands添加和删除书签

RACCommand *addBookmarkCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input){
        return [RACSignal return:input];
    }];
...
[addBookmarkCommand execute: bookmark1];
RACCommand *deleteBookmarkCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input){
        return [RACSignal return:input];
    }];
...
[deleteBookmarkCommand execute: bookmark2]

在用户交互上执行命令。如何组合bookmarksSignal和两个命令的执行信号来创建带有书签的NSArray的信号,该书签包含addBookmarkCommand添加的原始书签而没有deleteBookmarkCommand删除的书签?

(书签按url属性进行比较)

我担心我会遗漏一些明显的东西,但我无法弄清楚如何以纯粹的方式做到这一点。

2 个答案:

答案 0 :(得分:2)

由于时间不够,与@ IanHenry相比,我会选择更简洁的答案:)

对于任何没有意义的内容,请发表评论,我会很乐意详细解释。

// Map additions into blocks that add the bookmark to the given mutable array.
RACSignal *addBookmarkSignal = [[addBookmarkCommand concat] map:^(Bookmark *bookmark) {
    return ^(NSMutableArray *bookmarks) {
        return [bookmarks addObject:bookmark];
    };
}];

// Map deletions into blocks that remove the bookmark from the given mutable array.
RACSignal * addBookmarkSignal = [[deleteBookmarkCommand concat] map:^(Bookmark *bookmark) {
    return ^(NSMutableArray *bookmarks) {
        [bookmarks removeObject:bookmark];
    };
}];

// Combine the add and delete functions into a single signal.
RACSignal *updatesSignal = [RACSignal merge:@[addBookmarkSignal, addBookmarkSignal]];

RACSignal *updatedBookmarksSignal = [[bookmarksSignal
    // Each time bookmarksSignal sends an array, this -map: builds a
    // signal that updates the latest list of bookmarks, and sends it. 
    map:^(NSArray *bookmarks) {
        NSMutableArray *mutableBookmarks = [bookmarks mutableCopy];

        // Using the update blocks from the add/delete commands,
        // produce the modified list of bookmarks.
        return [[updatesSignal
            map:^(void (^update)(NSMutableArray *)) {
                update(mutableBookmarks);
                return [mutableBookmarks copy];
            }]
            startWith:bookmarks];
    }]
    // When bookmarksSignal sends anew, switch to the newest signal of updates.
    switchToLatest];

答案 1 :(得分:1)

让我们暂时忘记RACCommand,并假装我们只是要添加书签的信号和要删除的书签信号,我们想要根据这些信息创建一组书签信号。这是一个很好的起点,我们可以弄清楚如何在事后实现这些。

RACSignal *addedSignal = ...;
RACSignal *removedSignal = ...;

然后我们想要一个信号,即将这两个信号组合成一组所有添加的东西,之后没有任何东西被移除(如果我们想要,我们可以将它映射到NSArray。) / p>

RACSignal *bookmarkSetSignal = ...;

现在我们必须填写它。我们可以通过创建我们修改的可变集来优化这里,并且每次发生更改时只发送对同一集的引用。但这与信号的本质相反。让我们把这个优化保持一分钟,然后以纯粹的,功能性的方式进行。

我们将使用scanWithStart:reduce:方法,因为它完全适合这个问题。它就像一个fold,返回每个中间值,这正是我们想要的。

但首先我们必须使addedSignalremovedSignal有用。这是我的想法:将它们合并为一个信号,但是将另一个值附加到它们上,表示它是添加还是删除。

// turn a signal of bookmarks into a signal of tuples of the form (bookmark, isAdded)
RACSignal *changes = [RACSignal merge:@[[addedSignal map:^(id x) { return RACTuplePack(x, @YES); }],
                                        [removedSignal map:^(id x) { return RACTuplePack(x, @NO); }]]];

现在它只是一个信号,它会更容易纠结它。然后我们可以将这些更改折叠成单个值(所有更改的组合),除了我们报告的每一步。所以这是扫描,而不是折叠。但扫描是一个不好的动词。无论如何:

RACSignal *bookmarkSetSignal = [changes scanWithStart:[NSSet set] reduce:^(NSSet *running, RACTuple *next) {
    RACTupleUnpack(Bookmark *bookmark, NSNumber *isAddingNumber) = next;
    if (isAddingNumber.boolValue) {
        [running setByAddingObject:bookmark];
    } else {
        // you can do this with a much nicer helper, but this is the shortest way for this answer...
        return [running filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"url != %@", bookmark.url]];
    }
}];

大!我们从一个空集开始,每次发生更改时,我们都会通过添加或删除该元素来创建一个新集。 running始终是我们上次计算的任何内容(从空集开始),next是应该发生的更改的描述(书签+无论是否添加或删除)。我们现在有一组书签的信号,就像我们想要的那样!

现在我们需要填写addedSignalremovedSignal

我们这样做的确切方式有点......好吧,这取决于用户的互动。我们可以将每个人作为主题,然后用户交互将手动发送新值。这可能是正确的方法。它比手动触发RACCommand更清晰。无论如何,这是一个单独的问题。假设您现在拥有完全相同的RACCommand格式,我认为我们可以像这样实现它:

RACSignal *addedSignal = [self.addBookmarkCommand.executionSignals switchToLatest];
RACSignal *removedSignal = [self.deleteBookmarkCommand.executionSignals switchToLatest];

executionSignals是信号块中返回的每个信号的信号。这仅仅是[RACSignal return:bookmark]。通过switch ToLatest,我们基本上“解开”了这个价值。但是如果我们使用了一个主题,我们就不需要首先进行包装/解包。但无论如何,单独讨论。

这段代码几乎肯定需要做一些修改才能做你想做的事情(没有经过测试),但希望这会让你指出正确的方向。