简体   繁体   中英

Using ReactiveCocoa, how should I handle sending one item at a time (instead of a list of items) to the subscriber of a signal?

This question stems from this GitHub issue for the book Functional Reactive Pixels by Ash Furrow .

The example below is similar to the book in that it bridges non-RAC code and also sends the entire list of photos.

RACCommand *fetchPhotosCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input __unused) {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[MyAPIClient sharedClient] fetchPhotosWithBlock:^(NSArray *photos, NSError *error) {
            if (photos == nil) {
                [subscriber sendError:error];
            }
            else {                        
                [subscriber sendNext:photos];
                [subscriber sendCompleted];
            }
        }];

        return [RACDisposable disposableWithBlock:^{
            // Perform cleanup, cancel request, etc
        }];
    }];
}];

I'd like to know how best to send individual items (photos in this case), but more importantly, how the command should be subscribed to and the response handled, including pagination.

Typically I would do this...

RAC(self, photos) = [fetchPhotosCommand.executionSignals flatten];

...which obviously doesn't work when sending individual items. The book says this:

We're sending over a completed data set instead of a stream of single values over time. It would be “more reactive” if we instead sent a stream of individual photo models that could be concatenated later on. This would also help with pagination, but we're not going to address this pattern because it's a little more advanced. Check out octokit for an example of this concatenation.

So, I'm aware that some concatenation is needed on the subscribing end but don't follow what's happening in octokit.

In summary:

  • How should I be sending the individual items? Something like this?

     [photos enumerateObjectsUsingBlock:^(Photo *photo, NSUInteger idx __unused, BOOL *stop __unused) { [subscriber sendNext:photo]; }]; [sendCompleted]; return [RACDisposable disposableWithBlock:^{ // Also do something here with `stop`? }]; 
  • How should I handle the individual items on the subscribing end, so that self.photos is still an array of all of the photos that were sent individually?

  • How does pagination play a part in this? Obviously we wouldn't want to add items to self.photos that were already present.

How should I be sending the individual items? Something like this?

Your example is correct. If you want to send individual Photo objects, you find some way to iterate through your list of Photos and send the subscriber each Photo as a separate value.

How should I handle the individual items on the subscribing end, so that self.photos is still an array of all of the photos that were sent individually?

This sort of contradicts your desire to send them individually. If you want to collect them into an array in your subscriber, you can either send the Photos in batches as arrays, or you can collect the individual Photo values and build an array from them. (There's actually a built-in operator in ReactiveCocoa that will collect values into an array for you, called -[RACSignal collect] .)

How does pagination play a part in this? Obviously we wouldn't want to add items to self.photos that were already present.

I'm not sure how to help you with this. -enqueueRequest:resultClass:fetchAllPages: creates signals that paginate through results for you, following this general approach:

  1. The client (of this method) supplies a GitHub API URL and subscribes to the signal.
  2. This subscription causes OctoKit to make a request to GitHub's API.
  3. GitHub's API returns a response with a page of results. Also included in the response is a Link HTTP header, whose value is a URL that contains the next page of results.
  4. The response is deserialized into Objective-C objects.
  5. The signal sends each object to the subscriber (your code).
  6. If a Link header was present in the HTTP response, the -enqueueRequest:resultClass:fetchAllPages: method is recursively invoked to obtain a signal for the next page's URL, and that signal is concatenated onto the end of the current signal.

As @jspahrsummers points out, this lets you lazily traverse through the results. As your code reaches the end of each page, it will subscribe to the concatenated signal for the next page, resulting in another fetch for data. But it relies on OctoKit and GitHub API infrastructure. Hopefully this explanation helps you understand how you can modify your code to support a model similar to OctoKit's.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM