Отправить ошибку из блока doNext?

Я использую ReactiveCocoa и Overcoat/Mantle/AFNetworking для извлечения данных и аутентификации пользователя из RESTful API.

Это код в контроллере представления входа в систему, который управляет кнопкой входа в систему и текстовыми полями для учетных данных:

@weakify(self);
self.loginButton.rac_command =
[[RACCommand alloc] initWithEnabled:validCredentials
                        signalBlock:^RACSignal *(id input) {
                            @strongify(self);
                            return [[PFUserManager sharedManager] logInUser:self.usernameTextField.text
                                                            password:self.passwordTextField.text];
                        }];

// Handle errors for the login command
[self.loginButton.rac_command.errors subscribeNext:^(NSError *error) {
    // Present the error message
    [PFErrorAlertFactory showOVCError:error];
}];

// Take care of the signal from the request
[[self.loginButton.rac_command.executionSignals flatten] subscribeNext:^(NSNumber *success) {
    @strongify(self);
    [self clearTextFields];
    [self.flowController controllerForMainScreen]; // Transition to "logged in state"
} error:^(NSError *error) {
    @strongify(self);
    [self clearTextFields];
}];

У меня есть этот метод на одноэлементном классе UserManager:

- (RACSignal *)logInUser:(NSString *)username password:(NSString *)password {
    // Return a cold signal that sends next and complete when user is authenticated and error if authentication failed.

    PFAPIClient *client = [[PFAPIClient alloc] initWithUsername:username password:password];

    @weakify(self);
    RACSignal *loginSignal = [[client rac_POST:kAuthenticationResourcePath parameters:nil] doNext:^(OVCResponse *response) {
        @strongify(self);
        self.currentUser = response.result;
        NSError *error;
        [SSKeychain setPassword:password forService:kKeychainServiceKey account:self.currentUser.username error:&error];
        if (error) {
            [PFErrorAlertFactory showLocalizedDescriptionOfError:error];
        }
    }];

    return loginSignal;
}

Это все хорошо, используя этот сигнал в качестве сигнала RACCommand для кнопки. Я обрабатываю следующие, ошибки и завершенные события в контроллере представления входа в систему, и он работает нормально.

Как вы видите в коде UserManager, в блоке doNext я показываю ошибку, если метод Keychain возвращает ее. Я немного не уверен, относится ли эта обработка ошибок к этому классу.

Это работает, ошибка отображается как UIAlertView, но должен ли этот класс UserManager действительно отвечать за отображение ошибки?

Ошибки, поступающие из сигнала rac_POST, обрабатываются контроллером представления входа в систему, и я хотел бы также обработать ошибку из метода Keychain. Можно ли отправить ошибку абоненту сигнала rac_POST из блока doNext? Я пропускаю указатель на подписчика, хотя... Кроме того, если в методе цепочки для ключей происходит ошибка, сигнал все еще отправляет следующий и завершенный, и вход в систему успешен, насколько знает вызывающий контроллер представления. Это явно не так, как это должно работать.

Есть ли другой предпочтительный способ справиться с этой ситуацией? Я знаю, что побочные эффекты в блоках doNext не являются предпочтительными, но в этом случае я не вижу другого решения, так как я хочу, чтобы UserManager владел этим методом и мог устанавливать свой собственный currentUser. Должен ли я обернуть это в новый сигнал и явно вместо этого отправить next, complete и error?

С уважением, Йенс

1 ответ

Решение

Вы можете создать SSKeychain категория:

@interface SSKeychain (RACExtension)
- (RACSignal*)rac_setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;
@end

@implementation SSKeychain (RACExtension)

- (RACSignal*)rac_setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account 
{
    [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSError *error;
        BOOL result = [SSKeychain setPassword:password forService:service account:account error:&error];
        if (result) {
            [subscriber sendNext:@(result)];
            [subscriber sendCompleted];
        } else {
            [subscriber sendError:error];
        }
    }];
}

@end

А потом используй свой UserManager:

- (RACSignal *)logInUser:(NSString *)username password:(NSString *)password 
{
    PFAPIClient *client = [[PFAPIClient alloc] initWithUsername:username password:password];

    @weakify(self);
    return [[[client rac_POST:kAuthenticationResourcePath parameters:nil]
             flattenMap:^RACStream *(OVCResponse *response){
                 @strongify(self)
                 return [[SSKeychain rac_setPassword:password forService:kKeychainServiceKey account:self.currentUser.username]
                         mapReplace:response];
             }]
             doNext:^(OVCResponse *response){
                 @strongify(self)
                 self.currentUser = response.result;
             }]
}
Другие вопросы по тегам