Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive -flattenMap: with Reactive Cocoa + OctoKit (fetching dynamic object graph from web service)

I'm trying to pre-fetch an object graph of files in a Github repository using Octokit which depends on Reactive Cococa. I'm running into an issue creating a signal that will drill down, recursively, until there are no more directories to be fetched. Here is an example directory graph of a repository of mine (note: files have been omitted to keep the graph simple and clean).

Directory graph of RNGridMenu

- (RACSignal *)fetchContentTreeForRepository:(OCTRepository *)repository {
    return [[self fetchContent:Nil forRepository:repository parentContent:nil] doCompleted:^{
        // fetching tree finished, persist in database
    }];
}

- (RACSignal *)fetchContent:(OCTContent *)content forRepository:(OCTRepository *)repository parentContent:(OCTContent *)parentContent {
    return [[[[self.client fetchContents:content forRepository:repository] collect] deliverOn:RACScheduler.mainThreadScheduler]
            flattenMap:^RACSignal *(NSArray *fetchedContents) {
                // set the contents that were fetched
                id<OCTContentStoreProtocol>store = content ?: repository;
                store.contents = fetchedContents;

                // only search for contents of type "dir" (directory)
                NSArray *directories = [fetchedContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"contentType = \"dir\""]];
                NSMutableArray *signals;
                for (OCTContent *fetchedDir in directories) {
                    [signals addObject:[self fetchContent:fetchedDir forRepository:repository parentContent:content]];
                }
                return [RACSignal merge:signals];
            }];
}

As a note: -fetchContents:forRepository: builds a request path and returns a RACSignal that enqueues an HTTP request operation (it tries to follow the semantics OctoKit does).

The problem I'm running into currently is that this setup only executes the fetch for the contents of the repository (i.e. the top-level object in the graph). -flattenMap: is called, the array of signals is appropriately created, and the -merge: is returned. The intention is to create a recursive chain that ends when there are no more directory type children (should probably add a -filter: to check for that).

The intension is to fetch the entire graph of files for a Github repository and be notified when the operation is finished. Here's an example of how I'd like to handle calling this:

[[[GHDataStore sharedStore] fetchContentTreeForRepository:self.repository]
 subscribeNext:^(NSArray *contents) {
     // this would be called each time a new set of contents is received
     NSLog(@"received %i contents",[contents count]);
 }
 error:^(NSError *error) {
     NSLog(@"error fetching: %@",error.localizedDescription);
 }
 completed:^{
     // this would be called when mapping the graph is finished
     NSLog(@"finished fetching contents");
 }];

Any idea why it only executes the top level? I would have thought that calling -subscribeNext: on -fetchContentTreeForRepository: would be what executes the returned signal for -flattenMap:, but it seems I'm misunderstanding something. This assumption comes from the chaining example in the Reactive Cocoa readme.

edit: I am dumb.

like image 232
rnystrom Avatar asked Oct 04 '22 15:10

rnystrom


1 Answers

The issue is that you never initialize your NSMutableArray of signals:

NSMutableArray *signals;
for (OCTContent *fetchedDir in directories) {
       [signals addObject:[self fetchContent:fetchedDir forRepository:repository parentContent:content]];
}
return [RACSignal merge:signals];

Change that first line to NSMutableArray *signals = [NSMutableArray new] and it seems to work.

like image 95
mdiep Avatar answered Oct 07 '22 18:10

mdiep