Skip to content

Commit 7763d88

Browse files
authored
Merge pull request #735 from olejnjak/collection_and+or
Add possibility to use `and` + `or` with collection of signals/properties
2 parents 0ef101d + 77de7fd commit 7763d88

File tree

7 files changed

+314
-7
lines changed

7 files changed

+314
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# master
22
*Please add new entries at the top.*
3+
1. add possibility to use `all` and `any` operators with array of arguments (#735, kudos to @olejnjak)
4+
```swift
5+
let property = Property.any([boolProperty1, boolProperty2, boolProperty3])
6+
```
7+
2. Fixed Result extensions ambiguity (#733, kudos to @nekrich)
38
1. Fixed Result extensions ambiguity (#733, kudos to @nekrich)
49
1. Add `<~` binding operator to `Signal.Observer` (#635, kudos to @Marcocanc)
510

Sources/Property.swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ extension PropertyProtocol {
396396
extension PropertyProtocol where Value == Bool {
397397
/// Create a property that computes a logical NOT in the latest values of `self`.
398398
///
399-
/// - returns: A property that contains the logial NOT results.
399+
/// - returns: A property that contains the logical NOT results.
400400
public func negate() -> Property<Value> {
401401
return self.lift { $0.negate() }
402402
}
@@ -407,21 +407,41 @@ extension PropertyProtocol where Value == Bool {
407407
/// - parameters:
408408
/// - property: Property to be combined with `self`.
409409
///
410-
/// - returns: A property that contains the logial AND results.
410+
/// - returns: A property that contains the logical AND results.
411411
public func and<P: PropertyProtocol>(_ property: P) -> Property<Value> where P.Value == Value {
412412
return self.lift(SignalProducer.and)(property)
413413
}
414+
415+
/// Create a property that computes a logical AND between the latest values of `properties`.
416+
///
417+
/// - parameters:
418+
/// - property: Collection of properties to be combined.
419+
///
420+
/// - returns: A property that contains the logical AND results.
421+
public static func all<P: PropertyProtocol, Properties: Collection>(_ properties: Properties) -> Property<Value> where P.Value == Value, Properties.Element == P {
422+
return Property(initial: properties.map { $0.value }.reduce(true) { $0 && $1 }, then: SignalProducer.all(properties))
423+
}
414424

415425
/// Create a property that computes a logical OR between the latest values of `self`
416426
/// and `property`.
417427
///
418428
/// - parameters:
419429
/// - property: Property to be combined with `self`.
420430
///
421-
/// - returns: A property that contains the logial OR results.
431+
/// - returns: A property that contains the logical OR results.
422432
public func or<P: PropertyProtocol>(_ property: P) -> Property<Value> where P.Value == Value {
423433
return self.lift(SignalProducer.or)(property)
424434
}
435+
436+
/// Create a property that computes a logical OR between the latest values of `properties`.
437+
///
438+
/// - parameters:
439+
/// - properties: Collection of properties to be combined.
440+
///
441+
/// - returns: A property that contains the logical OR results.
442+
public static func any<P: PropertyProtocol, Properties: Collection>(_ properties: Properties) -> Property<Value> where P.Value == Value, Properties.Element == P {
443+
return Property(initial: properties.map { $0.value }.reduce(false) { $0 || $1 }, then: SignalProducer.any(properties))
444+
}
425445
}
426446

427447
/// A read-only property that can be observed for its changes over time. There

Sources/Signal.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,17 @@ extension Signal where Value == Bool {
21592159
///
21602160
/// - returns: A signal that emits the logical AND results.
21612161
public func and(_ signal: Signal<Value, Error>) -> Signal<Value, Error> {
2162-
return self.combineLatest(with: signal).map { $0.0 && $0.1 }
2162+
return type(of: self).all([self, signal])
2163+
}
2164+
2165+
/// Create a signal that computes a logical AND between the latest values of `booleans`.
2166+
///
2167+
/// - parameters:
2168+
/// - booleans: A collection of boolean signals to be combined.
2169+
///
2170+
/// - returns: A signal that emits the logical AND results.
2171+
public static func all<BooleansCollection: Collection>(_ booleans: BooleansCollection) -> Signal<Value, Error> where BooleansCollection.Element == Signal<Value, Error> {
2172+
return combineLatest(booleans).map { $0.reduce(true) { $0 && $1 } }
21632173
}
21642174

21652175
/// Create a signal that computes a logical OR between the latest values of `self`
@@ -2170,7 +2180,17 @@ extension Signal where Value == Bool {
21702180
///
21712181
/// - returns: A signal that emits the logical OR results.
21722182
public func or(_ signal: Signal<Value, Error>) -> Signal<Value, Error> {
2173-
return self.combineLatest(with: signal).map { $0.0 || $0.1 }
2183+
return type(of: self).any([self, signal])
2184+
}
2185+
2186+
/// Create a signal that computes a logical OR between the latest values of `booleans`.
2187+
///
2188+
/// - parameters:
2189+
/// - booleans: A collection of boolean signals to be combined.
2190+
///
2191+
/// - returns: A signal that emits the logical OR results.
2192+
public static func any<BooleansCollection: Collection>(_ booleans: BooleansCollection) -> Signal<Value, Error> where BooleansCollection.Element == Signal<Value, Error> {
2193+
return combineLatest(booleans).map { $0.reduce(false) { $0 || $1 } }
21742194
}
21752195
}
21762196

Sources/SignalProducer.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,7 +2693,7 @@ extension SignalProducer where Value == Bool {
26932693
///
26942694
/// - returns: A producer that emits the logical AND results.
26952695
public func and(_ booleans: SignalProducer<Value, Error>) -> SignalProducer<Value, Error> {
2696-
return combineLatest(with: booleans).map { $0.0 && $0.1 }
2696+
return type(of: self).all([self, booleans])
26972697
}
26982698

26992699
/// Create a producer that computes a logical AND between the latest values of `self`
@@ -2706,6 +2706,26 @@ extension SignalProducer where Value == Bool {
27062706
public func and<Booleans: SignalProducerConvertible>(_ booleans: Booleans) -> SignalProducer<Value, Error> where Booleans.Value == Value, Booleans.Error == Error {
27072707
return and(booleans.producer)
27082708
}
2709+
2710+
/// Create a producer that computes a logical AND between the latest values of `booleans`.
2711+
///
2712+
/// - parameters:
2713+
/// - booleans: A collection of boolean producers to be combined.
2714+
///
2715+
/// - returns: A producer that emits the logical AND results.
2716+
public static func all<BooleansCollection: Collection>(_ booleans: BooleansCollection) -> SignalProducer<Value, Error> where BooleansCollection.Element == SignalProducer<Value, Error> {
2717+
return combineLatest(booleans).map { $0.reduce(true) { $0 && $1 } }
2718+
}
2719+
2720+
/// Create a producer that computes a logical AND between the latest values of `booleans`.
2721+
///
2722+
/// - parameters:
2723+
/// - booleans: A collection of boolean producers to be combined.
2724+
///
2725+
/// - returns: A producer that emits the logical AND results.
2726+
public static func all<Booleans: SignalProducerConvertible, BooleansCollection: Collection>(_ booleans: BooleansCollection) -> SignalProducer<Value, Error> where Booleans.Value == Value, Booleans.Error == Error, BooleansCollection.Element == Booleans {
2727+
return all(booleans.map { $0.producer })
2728+
}
27092729

27102730
/// Create a producer that computes a logical OR between the latest values of `self`
27112731
/// and `producer`.
@@ -2715,7 +2735,7 @@ extension SignalProducer where Value == Bool {
27152735
///
27162736
/// - returns: A producer that emits the logical OR results.
27172737
public func or(_ booleans: SignalProducer<Value, Error>) -> SignalProducer<Value, Error> {
2718-
return combineLatest(with: booleans).map { $0.0 || $0.1 }
2738+
return type(of: self).any([self, booleans])
27192739
}
27202740

27212741
/// Create a producer that computes a logical OR between the latest values of `self`
@@ -2728,6 +2748,26 @@ extension SignalProducer where Value == Bool {
27282748
public func or<Booleans: SignalProducerConvertible>(_ booleans: Booleans) -> SignalProducer<Value, Error> where Booleans.Value == Value, Booleans.Error == Error {
27292749
return or(booleans.producer)
27302750
}
2751+
2752+
/// Create a producer that computes a logical OR between the latest values of `booleans`.
2753+
///
2754+
/// - parameters:
2755+
/// - booleans: A collection of boolean producers to be combined.
2756+
///
2757+
/// - returns: A producer that emits the logical OR results.
2758+
public static func any<BooleansCollection: Collection>(_ booleans: BooleansCollection) -> SignalProducer<Value, Error> where BooleansCollection.Element == SignalProducer<Value, Error> {
2759+
return combineLatest(booleans).map { $0.reduce(false) { $0 || $1 } }
2760+
}
2761+
2762+
/// Create a producer that computes a logical OR between the latest values of `booleans`.
2763+
///
2764+
/// - parameters:
2765+
/// - booleans: A collection of boolean producers to be combined.
2766+
///
2767+
/// - returns: A producer that emits the logical OR results.
2768+
public static func any<Booleans: SignalProducerConvertible, BooleansCollection: Collection>(_ booleans: BooleansCollection) -> SignalProducer<Value, Error> where Booleans.Value == Value, Booleans.Error == Error, BooleansCollection.Element == Booleans {
2769+
return any(booleans.map { $0.producer })
2770+
}
27312771
}
27322772

27332773
/// Represents a recoverable error of an observer not being ready for an

Tests/ReactiveSwiftTests/PropertySpec.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,26 @@ class PropertySpec: QuickSpec {
16391639
}
16401640
}
16411641

1642+
describe("all attribute") {
1643+
it("should emit true when all properties contain the same value") {
1644+
let property1 = MutableProperty(true)
1645+
let property2 = MutableProperty(true)
1646+
let property3 = MutableProperty(true)
1647+
expect(Property.all([property1, property2, property3]).value).to(beTrue())
1648+
}
1649+
1650+
it("should emit false when all properties contain opposite values") {
1651+
let property1 = MutableProperty(false)
1652+
let property2 = MutableProperty(true)
1653+
let property3 = MutableProperty(true)
1654+
expect(Property.all([property1, property2, property3]).value).to(beFalse())
1655+
}
1656+
1657+
it("should emit true when array of properties is empty") {
1658+
expect(Property.all([Property<Bool>]()).value).to(beTrue())
1659+
}
1660+
}
1661+
16421662
describe("or attribute") {
16431663
it("should emit true when at least one of the properties contains true") {
16441664
let property1 = MutableProperty(true)
@@ -1652,6 +1672,26 @@ class PropertySpec: QuickSpec {
16521672
expect(property1.or(property2).value).to(beFalse())
16531673
}
16541674
}
1675+
1676+
describe("any attribute") {
1677+
it("should emit true when at least one of the properties in array contains true") {
1678+
let property1 = MutableProperty(true)
1679+
let property2 = MutableProperty(false)
1680+
let property3 = MutableProperty(false)
1681+
expect(Property.any([property1, property2, property3]).value).to(beTrue())
1682+
}
1683+
1684+
it("should emit false when all properties in array contain false") {
1685+
let property1 = MutableProperty(false)
1686+
let property2 = MutableProperty(false)
1687+
let property3 = MutableProperty(false)
1688+
expect(Property.any([property1, property2, property3]).value).to(beFalse())
1689+
}
1690+
1691+
it("should emit false when array of properties is empty") {
1692+
expect(Property.any([Property<Bool>]()).value).to(beFalse())
1693+
}
1694+
}
16551695
}
16561696

16571697
describe("binding") {

Tests/ReactiveSwiftTests/SignalProducerSpec.swift

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,6 +2996,62 @@ class SignalProducerSpec: QuickSpec {
29962996
it("should be able to fallback to SignalProducer for contextual lookups") {
29972997
_ = SignalProducer<Bool, Never>.empty
29982998
.and(.init(value: true))
2999+
_ = SignalProducer<Bool, Never>.and(.init(value: true))
3000+
}
3001+
}
3002+
3003+
describe("all attribute") {
3004+
it("should emit true when all producers emit the same value") {
3005+
let producer1 = SignalProducer<Bool, Never> { observer, _ in
3006+
observer.send(value: true)
3007+
observer.sendCompleted()
3008+
}
3009+
let producer2 = SignalProducer<Bool, Never> { observer, _ in
3010+
observer.send(value: true)
3011+
observer.sendCompleted()
3012+
}
3013+
let producer3 = SignalProducer<Bool, Never> { observer, _ in
3014+
observer.send(value: true)
3015+
observer.sendCompleted()
3016+
}
3017+
3018+
SignalProducer.all([producer1, producer2, producer3]).startWithValues { value in
3019+
expect(value).to(beTrue())
3020+
}
3021+
}
3022+
3023+
it("should emit false when all producers emit opposite values") {
3024+
let producer1 = SignalProducer<Bool, Never> { observer, _ in
3025+
observer.send(value: true)
3026+
observer.sendCompleted()
3027+
}
3028+
let producer2 = SignalProducer<Bool, Never> { observer, _ in
3029+
observer.send(value: false)
3030+
observer.sendCompleted()
3031+
}
3032+
let producer3 = SignalProducer<Bool, Never> { observer, _ in
3033+
observer.send(value: false)
3034+
observer.sendCompleted()
3035+
}
3036+
3037+
SignalProducer.all([producer1, producer2, producer3]).startWithValues { value in
3038+
expect(value).to(beFalse())
3039+
}
3040+
}
3041+
3042+
it("should work the same way when using array of signals instead of an array of producers") {
3043+
let (signal1, observer1) = Signal<Bool, Never>.pipe()
3044+
let (signal2, observer2) = Signal<Bool, Never>.pipe()
3045+
let (signal3, observer3) = Signal<Bool, Never>.pipe()
3046+
SignalProducer.all([signal1, signal2, signal3]).startWithValues { value in
3047+
expect(value).to(beTrue())
3048+
}
3049+
observer1.send(value: true)
3050+
observer1.sendCompleted()
3051+
observer2.send(value: true)
3052+
observer2.sendCompleted()
3053+
observer3.send(value: true)
3054+
observer3.sendCompleted()
29993055
}
30003056
}
30013057

@@ -3047,6 +3103,64 @@ class SignalProducerSpec: QuickSpec {
30473103
it("should be able to fallback to SignalProducer for contextual lookups") {
30483104
_ = SignalProducer<Bool, Never>.empty
30493105
.or(.init(value: true))
3106+
_ = SignalProducer<Bool, Never>.or(.init(value: true))
3107+
}
3108+
}
3109+
3110+
describe("any attribute") {
3111+
it("should emit true when at least one of the producers in array emits true") {
3112+
let producer1 = SignalProducer<Bool, Never> { observer, _ in
3113+
observer.send(value: true)
3114+
observer.sendCompleted()
3115+
}
3116+
let producer2 = SignalProducer<Bool, Never> { observer, _ in
3117+
observer.send(value: false)
3118+
observer.sendCompleted()
3119+
}
3120+
let producer3 = SignalProducer<Bool, Never> { observer, _ in
3121+
observer.send(value: false)
3122+
observer.sendCompleted()
3123+
}
3124+
3125+
SignalProducer.any([producer1, producer2, producer3]).startWithValues { value in
3126+
expect(value).to(beTrue())
3127+
}
3128+
}
3129+
3130+
it("should emit false when all producers in array emit false") {
3131+
let producer1 = SignalProducer<Bool, Never> { observer, _ in
3132+
observer.send(value: false)
3133+
observer.sendCompleted()
3134+
}
3135+
let producer2 = SignalProducer<Bool, Never> { observer, _ in
3136+
observer.send(value: false)
3137+
observer.sendCompleted()
3138+
}
3139+
let producer3 = SignalProducer<Bool, Never> { observer, _ in
3140+
observer.send(value: false)
3141+
observer.sendCompleted()
3142+
}
3143+
3144+
SignalProducer.any([producer1, producer2, producer3]).startWithValues { value in
3145+
expect(value).to(beFalse())
3146+
}
3147+
}
3148+
3149+
it("should work the same way when using array of signals instead of an array of producers") {
3150+
let (signal1, observer1) = Signal<Bool, Never>.pipe()
3151+
let (signal2, observer2) = Signal<Bool, Never>.pipe()
3152+
let (signal3, observer3) = Signal<Bool, Never>.pipe()
3153+
let arrayOfSignals = [signal1, signal2, signal3]
3154+
3155+
SignalProducer.any(arrayOfSignals).startWithValues { value in
3156+
expect(value).to(beTrue())
3157+
}
3158+
observer1.send(value: true)
3159+
observer1.sendCompleted()
3160+
observer2.send(value: true)
3161+
observer2.sendCompleted()
3162+
observer3.send(value: true)
3163+
observer3.sendCompleted()
30503164
}
30513165
}
30523166

0 commit comments

Comments
 (0)