为什么在ReactiveCocoa中调用信号两次?

时间:2013-11-28 22:23:44

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

我正在使用https://github.com/ReactiveCocoa/ReactiveCocoa实现我的第一个代码。

用于登录用户。行[subscriber sendNext:user];被调用两次,但我希望只有一行。并且根本没有调用地图(因此永远不会调用自动登录)

这是我的实施:

-(RACSignal *) login:(NSString *)email pwd:(NSString *)pwd
{
    DDLogInfo(@"Login user %@", email);

    RACSignal *login = [RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber)
    {        
        [PFUser logInWithUsernameInBackground:email password:pwd block:^(PFUser *user, NSError *error) {

            if (error) {
                [subscriber sendError:error];
            } else {
                [subscriber sendNext:user];

                [subscriber sendCompleted];
            }
        }];

        return nil;
    }];

    [login map:^(PFUser *user) {
        return [self autoLogin:user];
    }];

    return login;
}

这样叫:

NSString *email = data[@"email"];
NSString *pwd = data[@"pwd"];
[SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeBlack];

RACSignal *login = [[SyncEngine server] login:email pwd:pwd];

[login
 subscribeCompleted:^
{
    [[NSNotificationCenter defaultCenter]
     postNotificationName:NOTIFY_LOGIN_CHANGED
     object:self];

     [SVProgressHUD showSuccessWithStatus:LOC_OK];


     [self cancelForm];
}];

[login
 subscribeError:^(NSError *error)
{
    [SVProgressHUD dismiss];

    [AppUrls alertError:LOC_ERROR_LOGING msg:error.userInfo[@"error"]];
}];

2 个答案:

答案 0 :(得分:10)

这是因为传递给+[RACSignal createSignal:]的块只要对信号进行订阅就会执行,并且您的代码会创建两个单独的订阅:

[login subscribeCompleted:^{ ... }];

[login subscribeError:^(NSError *error) { ... }];

如果您只想创建单个订阅,请使用方法-[RACSignal subscribeError:completed:]

[login subscribeError:^(NSError *error) {
        [SVProgressHUD dismiss];

        [AppUrls alertError:LOC_ERROR_LOGING msg:error.userInfo[@"error"]];
    }
    completed:^{
        [[NSNotificationCenter defaultCenter]
         postNotificationName:NOTIFY_LOGIN_CHANGED
         object:self];

         [SVProgressHUD showSuccessWithStatus:LOC_OK];


         [self cancelForm];
    }];

答案 1 :(得分:5)

虽然有时this solution可能就是您所需要的,但有时您确实希望确保只调用一次订阅块,可能是因为它会产生副作用。在这种情况下,您可以返回呼叫-replay的信号:

return [[RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber) {        
    [PFUser logInWithUsernameInBackground:email password:pwd block:^(PFUser *user, NSError *error) {

        if (error) {
            [subscriber sendError:error];
        } else {
            [subscriber sendNext:user];

            [subscriber sendCompleted];
        }
    }];

    return nil;
}] map:^(PFUser *user) {
    return [self autoLogin:user];
}] replay];

这个新的派生信号会向所有订阅者发送相同的消息或错误。如果信号完成,并且有新订户,则会立即收到所有消息。