ReactiveCocoa:正确使用信号检查实体可用性

ReactiveCocoa: Correct use of signals for checking entity availability

我正在尝试为以下场景编写响应式解决方案。

点击按钮,如果数据库中有一些实体可用,则必须将用户推送到新的视图控制器,否则应尝试下载这些实体并再次执行检查。

这是我目前得到的:

// 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 世界的新手,我不确定它是否真的正确或可以用不那么冗余的方式编写。

非常感谢, 丹

实际上,您不需要为按钮事件创建信号。 MVVM用于观察数据变化并自动改变UI。你可以这样写代码:

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
            }
        }];
    }
}];

当涉及 UI 控件时,我会诚实地建议不要直接绑定控件事件。如果您使用 RACCommand(在您的 viewModel 中声明和创建),您可以轻松地将 UI 绑定到下载状态(执行信号),在失败时显示警报消息并在成功时显示新的 UI(如果需要的话)。 您修改后的代码版本对我来说似乎不错,但我可能会简化您的内部信号:您不需要将布尔 "immediate" 变量包装在 return 信号周围(=您不需要知道您的 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)];
    }];
  }];