So, this is a pretty shitty case caused by the interaction of GCD and RAC. Strictly speaking, there is no mistake. But this surprising and strange. We talk about this requirement in the design guidelines https://github.com/ReactiveCocoa/ReactiveCocoa/blob/1bd47736f306befab64859602dbdea18f7f9a3f6/Documentation/DesignGuidelines.md#subscription-will-always-occurleron-occuronon
The key is that the subscription should always be done on a well-known scheduler . This is a requirement that RAC provides internally. If you just use a plain old GCD, there is no known scheduler, so the RAC should send the scheduler subscription asynchronously.
So, to go to your test:
[merged subscribeCompleted:^{ complete = YES; }]
The actual subscription happens asynchronously because there is no known scheduler. After that, the subscription ends after calls to- -sendCompleted , and it completely skips them. This is indeed a race condition, but apparently you will probably never succeed.
The fix is ββto use the RACScheduler instead of the GCD, if possible. If you need to use a specific GCD queue, you can use the RACTargetQueueScheduler . For example, a working, simplified version of your test:
-(void)test_merged_subjects_will_complete_if_on_gcd_queue{ __block BOOL complete = NO; dispatch_queue_t global_default_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"testScheduler" targetQueue:global_default_queue]; [scheduler schedule:^{ RACSubject *subject1 = [[RACSubject subject] setNameWithFormat:@"subject1"]; RACSubject *subject2 = [[RACSubject subject] setNameWithFormat:@"subject2"]; RACSignal *merged = [RACSignal merge:@[subject1, subject2]]; [merged subscribeCompleted:^{ complete = YES; }]; [subject1 sendCompleted]; [subject2 sendCompleted]; }]; NSDate *startTime = NSDate.date; do{ [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.5]]; }while(!complete && [NSDate.date timeIntervalSinceDate:startTime] <= 10.0); STAssertTrue(complete, nil); }
Since the subscription comes from the scheduler, subscribeCompleted: runs synchronously, receives completed events, and everything behaves as you expected.
If you donβt need to use a specific GCD queue and just want it to be done in a non-primary queue, do the following:
[[RACScheduler scheduler] schedule:^{ RACSubject *subject1 = [[RACSubject subject] setNameWithFormat:@"subject1"]; RACSubject *subject2 = [[RACSubject subject] setNameWithFormat:@"subject2"]; RACSignal *merged = [RACSignal merge:@[subject1, subject2]]; [merged subscribeCompleted:^{ complete = YES; }]; [subject1 sendCompleted]; [subject2 sendCompleted]; }];
I hope this clarifies what you see. Let me know if I need to rewrite something.