Skip to content

Commit bebb055

Browse files
authored
Merge pull request #362 from ReactiveCocoa/property-bugfix
Fixed rare occurences of `interrupted` emission in `Property`.
2 parents 51e7750 + 6ebb549 commit bebb055

File tree

2 files changed

+87
-42
lines changed

2 files changed

+87
-42
lines changed

Sources/Property.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,10 @@ public final class Property<Value>: PropertyProtocol {
518518
/// - values: A producer that will start immediately and send values to
519519
/// the property.
520520
public convenience init(initial: Value, then values: SignalProducer<Value, NoError>) {
521-
self.init(unsafeProducer: values.prefix(value: initial))
521+
self.init(unsafeProducer: SignalProducer { observer, disposables in
522+
observer.send(value: initial)
523+
disposables += values.start(Observer(mappingInterruptedToCompleted: observer))
524+
})
522525
}
523526

524527
/// Initialize a composed property that first takes on `initial`, then each
@@ -528,7 +531,7 @@ public final class Property<Value>: PropertyProtocol {
528531
/// - initialValue: Starting value for the property.
529532
/// - values: A signal that will send values to the property.
530533
public convenience init(initial: Value, then values: Signal<Value, NoError>) {
531-
self.init(unsafeProducer: SignalProducer(values).prefix(value: initial))
534+
self.init(initial: initial, then: SignalProducer(values))
532535
}
533536

534537
/// Initialize a composed property by applying the unary `SignalProducer`
@@ -627,15 +630,10 @@ public final class MutableProperty<Value>: ComposableMutablePropertyProtocol {
627630
/// followed by all changes over time, then complete when the property has
628631
/// deinitialized.
629632
public var producer: SignalProducer<Value, NoError> {
630-
return SignalProducer { [atomic, weak self] producerObserver, producerDisposable in
633+
return SignalProducer { [atomic, signal] producerObserver, producerDisposable in
631634
atomic.withValue { value in
632-
if let strongSelf = self {
633-
producerObserver.send(value: value)
634-
producerDisposable += strongSelf.signal.observe(producerObserver)
635-
} else {
636-
producerObserver.send(value: value)
637-
producerObserver.sendCompleted()
638-
}
635+
producerObserver.send(value: value)
636+
producerDisposable += signal.observe(Observer(mappingInterruptedToCompleted: producerObserver))
639637
}
640638
}
641639
}
@@ -696,3 +694,16 @@ public final class MutableProperty<Value>: ComposableMutablePropertyProtocol {
696694
observer.sendCompleted()
697695
}
698696
}
697+
698+
private extension Observer {
699+
convenience init(mappingInterruptedToCompleted observer: Observer<Value, Error>) {
700+
self.init { event in
701+
switch event {
702+
case .value, .completed, .failed:
703+
observer.action(event)
704+
case .interrupted:
705+
observer.sendCompleted()
706+
}
707+
}
708+
}
709+
}

Tests/ReactiveSwiftTests/PropertySpec.swift

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ class PropertySpec: QuickSpec {
332332

333333
var sentValue: String?
334334
var signalCompleted = false
335+
var hasUnexpectedEventsEmitted = false
335336

336337
constantProperty.producer.start { event in
337338
switch event {
@@ -340,12 +341,13 @@ class PropertySpec: QuickSpec {
340341
case .completed:
341342
signalCompleted = true
342343
case .failed, .interrupted:
343-
break
344+
hasUnexpectedEventsEmitted = true
344345
}
345346
}
346347

347348
expect(sentValue) == initialPropertyValue
348349
expect(signalCompleted) == true
350+
expect(hasUnexpectedEventsEmitted) == false
349351
}
350352
}
351353

@@ -359,6 +361,7 @@ class PropertySpec: QuickSpec {
359361
var signalSentValue: String?
360362
var producerCompleted = false
361363
var signalInterrupted = false
364+
var hasUnexpectedEventsEmitted = false
362365

363366
property.producer.start { event in
364367
switch event {
@@ -367,25 +370,24 @@ class PropertySpec: QuickSpec {
367370
case .completed:
368371
producerCompleted = true
369372
case .failed, .interrupted:
370-
break
373+
hasUnexpectedEventsEmitted = true
371374
}
372375
}
373376

374377
property.signal.observe { event in
375378
switch event {
376-
case let .value(value):
377-
signalSentValue = value
378379
case .interrupted:
379380
signalInterrupted = true
380-
case .failed, .completed:
381-
break
381+
case .value, .failed, .completed:
382+
hasUnexpectedEventsEmitted = true
382383
}
383384
}
384385

385386
expect(sentValue) == initialPropertyValue
386387
expect(signalSentValue).to(beNil())
387388
expect(producerCompleted) == true
388389
expect(signalInterrupted) == true
390+
expect(hasUnexpectedEventsEmitted) == false
389391
}
390392

391393
it("should retain the wrapped property") {
@@ -414,6 +416,7 @@ class PropertySpec: QuickSpec {
414416
var signalSentValue: String?
415417
var producerCompleted = false
416418
var signalInterrupted = false
419+
var hasUnexpectedEventsEmitted = false
417420

418421
property.producer.start { event in
419422
switch event {
@@ -422,25 +425,24 @@ class PropertySpec: QuickSpec {
422425
case .completed:
423426
producerCompleted = true
424427
case .failed, .interrupted:
425-
break
428+
hasUnexpectedEventsEmitted = true
426429
}
427430
}
428431

429432
property.signal.observe { event in
430433
switch event {
431-
case let .value(value):
432-
signalSentValue = value
433434
case .interrupted:
434435
signalInterrupted = true
435-
case .failed, .completed:
436-
break
436+
case .value, .failed, .completed:
437+
hasUnexpectedEventsEmitted = true
437438
}
438439
}
439440

440441
expect(sentValue) == initialPropertyValue
441442
expect(signalSentValue).to(beNil())
442443
expect(producerCompleted) == true
443444
expect(signalInterrupted) == true
445+
expect(hasUnexpectedEventsEmitted) == false
444446
}
445447

446448
it("should not retain the wrapped property, and remain accessible after its the property being reflected has deinitialized.") {
@@ -592,26 +594,43 @@ class PropertySpec: QuickSpec {
592594
describe("from a value and SignalProducer") {
593595
it("should initially take on the supplied value") {
594596
let property = Property(initial: initialPropertyValue,
595-
then: SignalProducer.never)
597+
then: SignalProducer.never)
596598

597599
expect(property.value) == initialPropertyValue
598600
}
599601

600602
it("should take on each value sent on the producer") {
601603
let property = Property(initial: initialPropertyValue,
602-
then: SignalProducer(value: subsequentPropertyValue))
604+
then: SignalProducer(value: subsequentPropertyValue))
603605

604606
expect(property.value) == subsequentPropertyValue
605607
}
606608

609+
it("should complete its producer and signal even if the upstream interrupts") {
610+
let (signal, observer) = Signal<String, NoError>.pipe()
611+
612+
let property = Property(initial: initialPropertyValue, then: SignalProducer(signal))
613+
614+
var isProducerCompleted = false
615+
var isSignalCompleted = false
616+
617+
property.producer.startWithCompleted { isProducerCompleted = true }
618+
property.signal.observeCompleted { isSignalCompleted = true }
619+
expect(isProducerCompleted) == false
620+
expect(isSignalCompleted) == false
621+
622+
observer.sendInterrupted()
623+
expect(isProducerCompleted) == true
624+
expect(isSignalCompleted) == true
625+
}
626+
607627
it("should return a producer and a signal that respect the lifetime of its ultimate source") {
608628
var signalCompleted = false
609629
var producerCompleted = false
610630
var signalInterrupted = false
611631

612632
let (signal, observer) = Signal<Int, NoError>.pipe()
613-
var property: Property<Int>? = Property(initial: 1,
614-
then: SignalProducer(signal))
633+
var property: Property<Int>? = Property(initial: 1, then: SignalProducer(signal))
615634
let propertySignal = property!.signal
616635

617636
propertySignal.observeCompleted { signalCompleted = true }
@@ -641,8 +660,7 @@ class PropertySpec: QuickSpec {
641660
it("should initially take on the supplied value, then values sent on the signal") {
642661
let (signal, observer) = Signal<String, NoError>.pipe()
643662

644-
let property = Property(initial: initialPropertyValue,
645-
then: signal)
663+
let property = Property(initial: initialPropertyValue, then: signal)
646664

647665
expect(property.value) == initialPropertyValue
648666

@@ -651,15 +669,31 @@ class PropertySpec: QuickSpec {
651669
expect(property.value) == subsequentPropertyValue
652670
}
653671

672+
it("should complete its producer and signal even if the upstream interrupts") {
673+
let (signal, observer) = Signal<String, NoError>.pipe()
674+
675+
let property = Property(initial: initialPropertyValue, then: signal)
676+
677+
var isProducerCompleted = false
678+
var isSignalCompleted = false
679+
680+
property.producer.startWithCompleted { isProducerCompleted = true }
681+
property.signal.observeCompleted { isSignalCompleted = true }
682+
expect(isProducerCompleted) == false
683+
expect(isSignalCompleted) == false
684+
685+
observer.sendInterrupted()
686+
expect(isProducerCompleted) == true
687+
expect(isSignalCompleted) == true
688+
}
654689

655690
it("should return a producer and a signal that respect the lifetime of its ultimate source") {
656691
var signalCompleted = false
657692
var producerCompleted = false
658693
var signalInterrupted = false
659694

660695
let (signal, observer) = Signal<Int, NoError>.pipe()
661-
var property: Property<Int>? = Property(initial: 1,
662-
then: signal)
696+
var property: Property<Int>? = Property(initial: 1, then: signal)
663697
let propertySignal = property!.signal
664698

665699
propertySignal.observeCompleted { signalCompleted = true }
@@ -1487,63 +1521,63 @@ class PropertySpec: QuickSpec {
14871521
break
14881522
}
14891523
}
1490-
1524+
14911525
expect(receivedValues) == [ "0" ]
1492-
1526+
14931527
outerProperty!.value = secondProperty!
14941528
secondProperty!.value = 11
14951529
outerProperty!.value = thirdProperty!
14961530
thirdProperty!.value = 21
1497-
1531+
14981532
expect(receivedValues) == [ "0", "10", "11", "20", "21" ]
14991533
expect(errored) == false
15001534
expect(completed) == false
1501-
1535+
15021536
secondProperty!.value = 12
15031537
secondProperty = nil
15041538
thirdProperty!.value = 22
15051539
thirdProperty = nil
1506-
1540+
15071541
expect(receivedValues) == [ "0", "10", "11", "20", "21", "22" ]
15081542
expect(errored) == false
15091543
expect(completed) == false
1510-
1544+
15111545
outerProperty = nil
15121546
expect(errored) == false
15131547
expect(completed) == true
15141548
}
15151549
}
15161550
}
1517-
}
1518-
1551+
}
1552+
15191553
describe("negated attribute") {
15201554
it("should return the negate of a value in a Boolean property") {
15211555
let property = MutableProperty(true)
15221556
expect(property.negate().value).to(beFalse())
15231557
}
15241558
}
1525-
1559+
15261560
describe("and attribute") {
15271561
it("should emit true when both properties contains the same value") {
15281562
let property1 = MutableProperty(true)
15291563
let property2 = Property(MutableProperty(true))
15301564
expect(property1.and(property2).value).to(beTrue())
15311565
}
1532-
1566+
15331567
it("should emit false when both properties contains opposite values") {
15341568
let property1 = MutableProperty(true)
15351569
let property2 = Property(MutableProperty(false))
15361570
expect(property1.and(property2).value).to(beFalse())
15371571
}
15381572
}
1539-
1573+
15401574
describe("or attribute") {
15411575
it("should emit true when at least one of the properties contains true") {
15421576
let property1 = MutableProperty(true)
15431577
let property2 = Property(MutableProperty(false))
15441578
expect(property1.or(property2).value).to(beTrue())
15451579
}
1546-
1580+
15471581
it("should emit false when both properties contains false") {
15481582
let property1 = MutableProperty(false)
15491583
let property2 = Property(MutableProperty(false))
@@ -1579,7 +1613,7 @@ class PropertySpec: QuickSpec {
15791613
observer.send(value: subsequentPropertyValue)
15801614
expect(mutableProperty.value) == initialPropertyValue
15811615
}
1582-
1616+
15831617
it("should tear down the binding when the property deallocates") {
15841618
var signal: Signal<String, NoError>? = {
15851619
let (signal, _) = Signal<String, NoError>.pipe()

0 commit comments

Comments
 (0)