ReactiveCocoa: правильное использование сигналов для проверки доступности объекта

Я пытаюсь написать реактивное решение для следующего сценария.

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

Вот что у меня так далеко:

// VIEW CONTROLLER
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return self.viewModel.rac_checkEntitiesAvailability;
    }];

[self.button.rac_command.executionSignals.flatten subscribeNext:^(id x) {
    if([x boolValue] == YES) {
        // Entities available, can perform segue
    } else {
        // Error
}];

// VIEW-MODEL
- (RACSignal*)rac_checkEntitiesAvailability {
    return [[RACSignal return:@([Entity MR_countOfEntities] > 0)]
            flattenMap:^RACStream *(id entitiesAvailable) {
                if([entitiesAvailable boolValue]) {
                    return [RACSignal return:@YES];
                } else {
                    return [[[self rac_downloadEntities] flattenMap:^RACStream *(id value) {
                        // This takes into account network problems too
                        return [RACSignal return:@([Entity MR_countOfEntities] > 0)];
                    }];
                }
            }];
}

Кажется, это работает, но, поскольку я новичок в мире ReactiveCocoa, я не уверен, что он действительно правильный или может быть написан менее излишним способом.

Большое спасибо, ДАН

2 ответа

Решение

Я бы честно посоветовал не использовать прямую привязку событий управления, когда задействованы UIControls. Если вы используете RACCommand (объявленный и созданный в вашей viewModel), вы можете легко привязать свой пользовательский интерфейс к состоянию загрузки (выполнение сигнала), показывать предупреждающие сообщения при сбоях и представлять новый пользовательский интерфейс при успехе (при необходимости). Ваша исправленная версия кода кажется мне хорошей, но я бы, вероятно, упростил ваш внутренний сигнал: вам не нужно оборачивать логическую "немедленную" переменную вокруг возвращаемого сигнала (= вам не нужен сигнал, чтобы знать, сколько сущности, которые есть в вашей модели CoreData, это синхронная операция). Примерно так (проверьте синтаксис, это может быть неправильно со всеми этими квадратными скобками)

[RACSignal defer:^RACSignal *{
    if([Entity MR_countOfEntities] > 0) {
        return [RACSignal return:@YES];
    } else {
        return [[self rac_downloadEntities] flattenMap:^RACStream *(id value) {
            // This takes into account network problems too
            return [RACSignal return:@([Entity MR_countOfEntities] > 0)];
    }];
  }];

На самом деле, вам не нужно создавать сигнал для событий кнопки. MVVM используется для наблюдения за изменениями данных и автоматического изменения пользовательского интерфейса. Вы можете написать такие коды:

button.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    if (xxx) {
        // Entities available, can perform segue
    } else {
        // Error
    }
    return [RACSignal empty];
}];

Или вы можете обрабатывать события напрямую:

[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    if (xxx) {
        // Entities available, can perform segue
    } else {
        // Error
    }
}];

Не пытайтесь решить все проблемы через RAC.

===========

Для вашего сценария вы должны использовать блок. Добавьте функцию загрузки следующим образом:

@interface Entity ()

+ (int)MR_countOfEntities;
+ (void)MR_downloadEntites:(void(^)(void))finishBlock;

@end

@implementation Entity

static int _MR_countOfEntities = 0;
+ (int)MR_countOfEntities
{
    return _MR_countOfEntities;
}

+ (void)MR_downloadEntites:(void (^)(void))finishBlock
{
    // Download entites. This is an example.
    [[AFHTTPRequestOperationManager manager] GET:@"http://test.com/entites" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        _MR_countOfEntities = 1;
        finishBlock();
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        _MR_countOfEntities = 0;
        finishBlock();
    }];
}

@end

Таким образом, вы можете изменить коды кнопки следующим образом:

[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    if ([Entity MR_countOfEntities] > 1) {
        // Entities available, can perform segue
    } else {
        [Entity MR_downloadEntites:^{
            if ([Entity MR_countOfEntities] > 1) {
                // Entities available, can perform segue
            } else {
                // Error
            }
        }];
    }
}];
Другие вопросы по тегам