Как вы могли бы написать выборку "Reactive Cocoa"?

Клиент, который я создаю, использует Reactive Cocoa с Octokit, и пока все идет хорошо. Однако теперь я нахожусь в точке, где я хочу получить коллекцию репозиториев, и у меня возникают проблемы с обволакиванием вокруг этого "RAC way"

// fire this when an authenticated client is set
[[RACAbleWithStart([GHDataStore sharedStore], client) 
  filter:^BOOL (OCTClient *client) {
      return client != nil && client.authenticated;
  }]
 subscribeNext:^(OCTClient *client) {
     [[[client fetchUserRepositories] deliverOn:RACScheduler.mainThreadScheduler]
      subscribeNext:^(OCTRepository *fetchedRepo) {
          NSLog(@" Received new repo: %@",fetchedRepo.name);
      }
      error:^(NSError *error) {
          NSLog(@"Error fetching repos: %@",error.localizedDescription);
      }];
 } completed:^{
     NSLog(@"Completed fetching repos");
 }];

Первоначально предполагалось, что -subscribeNext: передаст NSArray, но теперь понимает, что он отправляет сообщение каждый возвращаемый объект, который в этом случае является OCTRepository.

Теперь я мог бы сделать что-то вроде этого:

NSMutableArray *repos = [NSMutableArray array];
// most of that code above
subscribeNext:^(OCTRepository *fetchedRepo) {
    [repos addObject:fetchedRepo];
}
// the rest of the code above

Конечно, это работает, но, похоже, не соответствует функциональным принципам, которые позволяет RAC. Я действительно пытаюсь придерживаться конвенций здесь. Любой свет на возможности RAC/Octokit приветствуются!

Ответ 1

В значительной степени это зависит от того, что вы хотите делать с репозиториями позже. Кажется, что вы хотите что-то сделать, когда у вас есть все репозитории, поэтому я создам пример, который делает это.

// Watch for the client to change
RAC(self.repositories) = [[[[[RACAbleWithStart([GHDataStore sharedStore], client) 
    // Ignore clients that aren't authenticated
    filter:^ BOOL (OCTClient *client) {
        return client != nil && client.authenticated;
    }]
    // For each client, execute the block. Returns a signal that sends a signal
    // to fetch the user repositories whenever a new client comes in. A signal of
    // of signals is often used to do some work in response to some other work.
    // Often times, you'd want to use `-flattenMap:`, but we're using `-map:` with
    // `-switchToLatest` so the resultant signal will only send repositories for
    // the most recent client.
    map:^(OCTClient *client) {
        // -collect will send a single value--an NSArray with all of the values
        // that were send on the original signal.
        return [[client fetchUserRepositories] collect];
    }]
    // Switch to the latest signal that was returned from the map block.
    switchToLatest]
    // Execute a block when an error occurs, but don't alter the values sent on
    // the original signal.
    doError:^(NSError *error) {
        NSLog(@"Error fetching repos: %@",error.localizedDescription);
    }]
    deliverOn:RACScheduler.mainThreadScheduler];

Теперь self.repositories изменит (и запустит уведомление KVO), когда репозитории будут обновлены от клиента.

Несколько вещей, чтобы отметить об этом:

  • Лучше избегать subscribeNext:, когда это возможно. Используя его шаги вне функциональной парадигмы (как и doNext: и doError:, но они также являются полезными инструментами порой). В общем, вы хотите подумать о том, как вы можете преобразовать сигнал во что-то, что делает то, что вы хотите.

  • Если вы хотите связать одну или несколько частей совместной работы, вы часто хотите использовать flattenMap:. В более общем плане вы хотите начать думать о сигналах сигналов - сигналах, которые посылают другие сигналы, которые представляют другую работу.

  • Вы часто хотите подождать как можно дольше, чтобы вернуться к основному потоку.

  • Когда вы размышляете над проблемой, иногда полезно начинать с написания каждого отдельного сигнала, чтобы думать о: a) о том, что у вас есть, б) о том, чего вы хотите, и c) как добраться от одного к другому.

РЕДАКТИРОВАТЬ: Обновлено, чтобы отправить комментарий @JustinSpahrSummers ниже.

Ответ 2

Существует оператор -collect, который должен делать именно то, что вы ищете.

// Collect all receiver `next`s into a NSArray. nil values will be converted
// to NSNull.
//
// This corresponds to the `ToArray` method in Rx.
//
// Returns a signal which sends a single NSArray when the receiver completes
// successfully.
- (RACSignal *)collect;