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

时间:2015-03-12 09:10:50

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

我在新的iOS应用中使用ReactiveCocoa。我是反应式编程的新手,所以我仍然试图了解链接信号的正确方法。 现在我有以下流程用于"登录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];
}];

在我的视图控制器中,我处理表示逻辑,我在显示进度点的同时阻止界面并最终报告错误或者如果一切正常则超过登录屏幕:

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。什么是正确的方法? 谢谢!

更新 我尝试将所有逻辑移动到RACCommand内的ViewModel,但我仍然需要捕捉errors内发生的错误。 订阅RACCommand信号不是一个选项,因为completed仍会返回{{1}}事件,因此我的演示逻辑无法判断一切是否正常。 我没有尝试在RACCommand中设置一个BOOL,如果出现错误会产生副作用,并在视图中观察它。但无论如何,这种做法似乎有些过时了。

2 个答案:

答案 0 :(得分:1)

您可以使用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块移到RACCommand,以确保永远不会发生(因为RACCommand不会再次执行,直到前一个完成执行。)虽然没有看到更多的代码,但我真的可以说这是否是一个合理的变化。

进一步清理这是一个棘手的事情,因为它具有固有的副作用。如果你为ALTAlert类创建一个反应桥,你可以清理很多这些订阅,就像你可以说&#34;看看这个信号信号,让你的状态反映出来。&# 34;然后你可以传递执行信号,而不必担心在这里做一些更大的事情。

然后你唯一真实的副作用是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];
}];

答案 1 :(得分:0)

不确定您是否看过设计指南,但这些指示了一些如何避免-subscribeNext:error:completed:模式的解决方案。具体来说是these

  
      
  • RAC()或RACChannelTo()宏可用于将信号绑定到属性,而不是在发生更改时执行手动更新。
  •   
  • -rac_liftSelector:withSignals:方法可用于在一个或多个信号触发时自动调用选择器。
  •   
  • 像-takeUntil:这样的运算符可用于在事件发生时自动处理订阅(例如在UI中按下&#34;取消&#34;按钮)。
  •