ios - 如何使用 Reactive Cocoa 以正确的方式链接信号?

标签 ios objective-c reactive-programming reactive-cocoa

我在新的 iOS 应用程序中使用 ReactiveCocoa。我是响应式(Reactive)编程的新手,所以我仍在尝试了解什么是链接信号的正确方法。 现在我有以下“使用 Twitter 登录”按钮的流程。

ALTUserManager 类具有以下方法来管理整个登录阶段,方法是调用库中的一些函数,该库提供 Twitter 登录面板并执行所有 OAuth 操作:

- (RACSignal *)loginTwitter:(UIViewController *)vc {
    RACSignal *loginSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[ALTTwitter sharedInstance]isLoggedIn:^(BOOL loggedIn) {
            if(loggedIn){
                [subscriber sendCompleted];
            }
            else{
                [[ALTTwitter sharedInstance]login:vc andSuccess:^{
                    [subscriber sendCompleted];
                } failure:^(NSString *error) {
                    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
                    userInfo[NSLocalizedDescriptionKey] = error;
                    [subscriber sendError:[NSError errorWithDomain:@"" code:1 userInfo:userInfo]];
                }];
            }
        }];
        return nil;
    }];
    return loginSignal;
}

我使用的是 MVVM 模式,所以在我的 ViewModel 中,我在其 init 方法中添加了以下命令:

self.twitterLoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [[ALTUserManager sharedInstance] loginTwitter:nil];
}];

在我的 View Controller 中,我正在处理表示逻辑,我在显示进度 HUD 时阻塞界面,并最终报告错误或如果一切正常则跳过登录屏幕:

self.twBtn.rac_command = self.viewModel.twitterLoginCommand;
[self.viewModel.twitterLoginCommand.executionSignals subscribeNext:^(id x) {
    NSLog(@"%@", x);
    [x subscribeCompleted:^{
        NSLog(@"%@", @"completed");
        [ALTAlert wait:@""];
        [[self.viewModel appLoginWithTwitter] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        } error:^(NSError *error) {
            [ALTAlert dismiss];
            [ALTAlert error:error.localizedDescription];
        } completed:^{
            [ALTAlert dismiss];
            @strongify(self);
            [self goToChart];
        }];
    }];
}];
[self.viewModel.twitterLoginCommand.errors subscribeNext:^(NSError *error) {
    NSLog(@"Login error: %@", error);
    [ALTAlert dismiss];
    [ALTAlert error:error.localizedDescription];
}];

我很确定这可以用更好的方式重写。我主要关心的是 [x subscribeCompleted] 行。什么是正确的方法? 谢谢!

更新 我尝试将所有逻辑移动到 RACCommand 内的 ViewModel,但我仍然需要捕获 RACCommand 内发生的错误。 订阅 errors 信号不是一个选项,因为 RACCommand 仍然会返回 completed 事件,从而使我的表示逻辑无法判断是否一切顺利。 我还没有尝试在 RACCommand 中设置一个 BOOL 并在出现错误时产生副作用并在 View 中观察它。但无论如何,这种方法似乎有点老套。

最佳答案

您可以使用 then 帮助程序稍微简化嵌套,这将简化错误处理并防止单独的 twitterLoginCommand.errors 订阅:

[self.viewModel.twitterLoginCommand.executionSignals subscribeNext:^(id x) {
    [x then:^{
        NSLog(@"%@", @"completed");
        [ALTAlert wait:@""];
        return [self.viewModel appLoginWithTwitter];
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    } error:^(NSError *error) {
        [ALTAlert dismiss];
        [ALTAlert error:error.localizedDescription];
    } completed:^{
        [ALTAlert dismiss];
        @strongify(self);
        [self goToChart];
    }];
}];

不过,这有点奇怪。因为如果 twitterLoginCommandappLoginWithTwitter 信号完成之前再次触发,您可能会进入奇怪的状态。考虑到应用程序的其余部分,这可能是不可能的,但仅孤立地查看此代码块是我会担心的事情。

更好的做法可能是将 then block 移动到 RACCommand 中,以确保这种情况永远不会发生(作为 RACCommand 在前一个执行完之前不会再次执行。)虽然没有看到更多代码,但我不能真正说这是否是一个合理的更改。

这是一件很难进一步清理的事情,因为它本身就有副作用。如果您为 ALTAlert 类创建一个响应式桥接器,您可以清除其中的许多订阅,因为您可以只说“查看这个信号中的信号,并让您的状态反射(reflect)它”。然后你可以只传递执行信号,而不必担心在这里做一些更粗暴的事情。

那么你唯一真正的副作用是 goToChart,你可以做一些更简单的事情:

[[[self.viewModel.twitterLoginCommand.executionSignals flattenMap:^(id x) {
    return [x materialize];
}] filter:^(RACEvent *event) {
    return event.eventType == RACEventTypeCompleted;
}] subscribeNext:^(id x) {
    @strongify(self);
    [self goToChart];
}];

关于ios - 如何使用 Reactive Cocoa 以正确的方式链接信号?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29005652/

相关文章:

java - Spring webflux : how to publish event from sync call for async processing?

ios - UIImageView 纵横比?

javascript - 在组件中动态绑定(bind)数据到vue模型

ios - NSData 异常 - "incomprehensible archive"

objective-c - 强制洗牌 NSMutableArray

ios - 如何检索由 NSStrings 表示的命名对象?

objective-c - 用于在 Objective-C 中定义 kvc 评估器的宏

javascript - RXJS:第一次拖动后捕获拖动事件不起作用

ios - 在不重启应用程序的情况下更改 iOS 中的应用程序语言

ios - swift:将屏幕方向重置为纵向