Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the signal is called twice in ReactiveCocoa?

I'm implementing my first code with https://github.com/ReactiveCocoa/ReactiveCocoa.

Is for login a user. The line [subscriber sendNext:user]; is called twice, but I expect to be only one. And the map is not called at all (so autologin is never called)

This is my implementation:

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

Is called this way:

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"]];
}];
like image 695
mamcx Avatar asked Nov 28 '13 22:11

mamcx


2 Answers

This happens because the block passed to +[RACSignal createSignal:] executes whenever a subscription to the signal is made, and your code creates two separate subscriptions:

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

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

If you only want to create a single subscription, use the method -[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];
    }];
like image 52
erikprice Avatar answered Nov 15 '22 00:11

erikprice


While sometimes this solution might be all you need, sometimes you do want to make sure the subscription block is only called once, maybe because it produces side effects. In this case, you can return the signal calling -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];

This new, derived signal will send the the same messages or error to all subscribers. If the signal completes, and there is a new subscriber, this will immediately receive all messages as well.

like image 36
NachoSoto Avatar answered Nov 14 '22 23:11

NachoSoto