Let's forget about RACCommand
for a second, and pretend that we just have a signal of bookmarks to add and a signal of bookmarks to remove and we want to create a signal of sets of bookmarks based on that. It's a good starting point, and we can figure out how to implement those after the fact.
RACSignal *addedSignal = ...;
RACSignal *removedSignal = ...;
Then we want a signal that's the combination of those two signals into a single set of all the added things without anything removed afterwards (we can map it into an NSArray
after the fact if we want to).
RACSignal *bookmarkSetSignal = ...;
Now we have to fill it in. We could optimize here by making a mutable set that we modify and just send references to that same set every time a change happens. But that's sort of contrary to the nature of signals. Let's put that optimization on hold for a minute and do it the pure, functional way.
We're going to use the scanWithStart:reduce:
method, because it fits this problem perfectly. It's like a fold
that returns every intermediate value, which is exactly what we want.
But first we have to make addedSignal
and removedSignal
useful. Here's my idea: merge them into a single signal, but attach another value to them that says whether it's an add or a remove.
// 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); }]]];
Now that it's just one signal, it'll be a little easier to wrangle it. Then we can fold those changes into a single value (the composition of all the changes), except that we're reporting every step of the way. So it's a scan, not a fold. But scan makes a bad verb. Anyway:
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]];
}
}];
Great! We started with an empty set, and every time a change occurred we created a new set by adding or removing that element. running
is always whatever we computed last time (starting from the empty set), and next
is the description of the change that should happen (a bookmark + whether or not it was being added or removed). We now have a signal of sets of bookmarks, just like we wanted!
Except now we need to fill out addedSignal
and removedSignal
.
The exact way we do this is a little...well, it depends on the user interaction. We could make each one a subject, and then user interaction would manually send new values. That might be the right way to do it. It's cleaner than manually triggering an RACCommand
. Anyway, that's a separate question. Assuming the exact RACCommand format that you have now, I think we can implement it like this:
RACSignal *addedSignal = [self.addBookmarkCommand.executionSignals switchToLatest];
RACSignal *removedSignal = [self.deleteBookmarkCommand.executionSignals switchToLatest];
executionSignals
is a signal of every signal returned in the signal block. Which are merely [RACSignal return:bookmark]
. By switch
ingToLatest
, we're basically "unwrapping" that value. But if we used a subject we wouldn't need to wrap/unwrap in the first place. But anyway, separate discussion.
This code will almost certainly require a little modification to do what you want (haven't tested it), but hopefully this'll point you in the right direction idea-wise.