2.0.0
This is the second major release of ReactiveSwift. It requires Swift 3.1 (Xcode 8.3.x), and preliminarily supports Swift 3.2 and Swift 4.0 (Xcode 9).
Highlights
Safer Signal lifetime semantics (#355, #463)
The Signal lifetime semantics have been updated to improve interoperability with memory debugging tools. ReactiveSwift 2.0 adopted a new Signal internal which does not exploit deliberate retain cycles that consequentially confuse memory debugging tools.
ReactiveSwift 2.0 automatically terminates a Signal, when:
- (New) its input observer of the
Signalis not retained; OR - the
Signalis neither retained nor has any active observer;
Input observer refers to the
ObservertheSignalreceives input from. It is created and passed to you bySignal.init,Signal.pipeandSignalProducer.init.
Specifically, when an input Observer deinitializes, semantically it implies the associated Signal has no further event to be delivered. So ReactiveSwift would now interrupt the Signal automatically, and release all the associated resources.
func scopedSignal() -> Signal<Never, NoError> {
// Note that the `Observer` is discarded immediately.
let (signal, _) = Signal<Never, NoError>.pipe()
return signal
}
var isInterrupted = false
withExtendedLifetime(scopedSignal()) { signal in
signal.observeInterrupted { isInterrupted = true }
// ReactiveSwift 1.x:
// The `Signal` is still alive, probably forever unless the observer is detached.
expect(isInterrupted) == false
// ReactiveSwift 2.0:
// The `Signal` is automatically interrupted, since the deinitialization of the
// input `Observer` implies no further event would be sent.
expect(isInterrupted) == true
}Similarly for a deinitialised Signal, since no further observation can ever be made, ReactiveSwift would dispose of it when it knows for certain it has no active observer. Note that this is already the case for ReactiveSwift 1.x.
let checkpoint = AnyDisposable()
let escaped = CompositeDisposable()
func scopedObserver() -> Signal<Never, NoError>.Observer {
// Note that the `Signal` does not escape the scope.
let (signal, observer) = Signal<Never, NoError>.pipe(disposable: checkpoint)
escaped += signal.observe(Observer())
return observer
}
withExtendedLifetime(scopedObserver()) {
escaped.dispose()
// ReactiveSwift 1.x and 2.0:
// Since no further observation can be made to the `Signal`, and it has no
// active observer at this point, the `Signal` is automatically disposed of.
expect(checkpoint.isDisposed) == true
}In short, the Signal terminates when either of its ends implicitly declare their lack of interest — derived from the deinitialization of the Signal or the input Observer — to send or receive events. This makes ReactiveSwift more ARC friendly than before.
It is expected that memory debugging tools would no longer report irrelevant negative leaks that were once caused by the ReactiveSwift internals.
SignalProducer resource management (#334)
SignalProducer now uses Lifetime for resource management. You may observe the Lifetime for the disposal of the produced Signal. You may also continue to use the += convenience on Lifetime for adding Disposables.
let producer = SignalProducer<Int, NoError> { observer, lifetime in
lifetime += numbers.observe(observer)
}If you need to interrupt the SignalProducer, you should now do it through the input Observer:
let producer = SignalProducer<Int, NoError> { observer, _ in
observer.sendInterrupted()
}Reduced overhead for all SignalProducer lifted operators. (#140)
All SignalProducer lifted operators no longer yield an extra Signal. As a result, the cost of event delivery has been considerably reduced, and SignalProducer is generally as performant as Signal.
N-ary SignalProducer operators with generic operands (#410)
N-ary SignalProducer operators are now generic and accept any type that can be expressed as SignalProducer. Types may conform to SignalProducerConvertible to become an eligible operand.
For example:
let property = MutableProperty<Int>(0)
let producer = SignalProducer<Int, NoError>.never
let signal = Signal<Int, NoError>.never
/// Valid in ReactiveSwift 2.0.
_ = SignalProducer.combineLatest(property, producer, signal)Changes
Signal and SignalProducer
-
All
SignalandSignalProduceroperators now belongs to the respective concrete types. (#304)Custom operators should extend the concrete types directly.
SignalProtocolandSignalProducerProtocolshould be used only for constraining associated types. -
combineLatestandzipare optimised to have a constant overhead regardless of arity, mitigating the possibility of stack overflow. (#345, #471, kudos to @stevebrambilla for catching a bug in the implementation) -
When composing
SignalandSignalProducerof inhabitable types, e.g.NeverorNoError, ReactiveSwift now warns about operators that are illogical to use, and traps at runtime when such operators attempt to instantiate an instance. (#429, kudos to @andersio) -
interruptednow respectsobserve(on:). (#140)When a
SignalProduceris interrupted, ifobserve(on:)is the last applied operator,interruptedwould now be delivered on theSchedulerpassed toobserve(on:)just like other events. -
flatMap(_:transform:)is renamed toflatMap(_:_:). (#339) -
promoteErrors(_:)is renamed topromoteError(_:). (#408) -
Eventis renamed toSignal.Event. (#376) -
Observeris renamed toSignal.Observer. (#376)
Action
-
Action(input:_:),Action(_:),Action(enabledIf:_:)andAction(state:enabledIf:_:)are renamed toAction(state:execute:),Action(execute:),Action(enabledIf:execute:)andAction(state:enabledIf:execute:)respectively. (#325) -
Feedbacks from
isEnabledandisExecutingto the state of the sameAction, including allenabledIfconvenience initializers, no longer deadlocks. (#400, kudos to @andersio)Note that legitimate feedback loops would still deadlock.
-
Added new convenience initialisers to
Actionthat make creating actions with state input properties easier. When creating anActionthat is conditionally enabled based on an optional property, use the renamedAction.init(unwrapping:execute:)initialisers. (#455, kudos to @sharplet)
Properties
-
The memory overhead of property composition has been considerably reduced. (#340)
-
MutablePropertynow enforces exclusivity of access. (#419, kudos to @andersio)In other words, nested modification in
MutableProperty.modifyis now prohibited. Generally speaking, it should have extremely limited impact as in most cases theMutablePropertywould have been deadlocked already. -
ValidationResultandValidatorOutputhave been renamed toValidatingProperty.ResultandValidatingProperty.Decision, respectively. (#443)
Bindings
-
The
BindingSourcenow requires only a producer representation ofself. (#359) -
The
<~operator overloads are now provided byBindingTargetProvider. (#359)
Disposables
-
SimpleDisposableandActionDisposablehas been folded intoAnyDisposable. (#412) -
CompositeDisposable.DisposableHandleis replaced byDisposable?. (#363) -
The
+=operator overloads forCompositeDisposableare now hosted inside the concrete types. (#412)
Bag
Schedulers
Schedulergains a class bound. (#333)
Lifetime
Lifetime.endednow uses the inhabitableNeveras its value type. (#392)
Atomic
SignalandAtomicnow useos_unfair_lockwhen it is available. (#342)
Additions
-
FlattenStrategy.raceis introduced. (#233, kudos to @inamiy)raceflattens whichever inner signal that first sends an event, and ignores the rest. -
FlattenStrategy.concurrentis introduced. (#298, kudos to @andersio)concurrentstarts and flattens inner signals according to the specified concurrency limit. If an inner signal is received after the limit is reached, it would be queued and drained later as the in-flight inner signals terminate. -
New operators:
reduce(into:)andscan(into:). (#365, kudos to @ikesyo)These variants pass to the closure an
inoutreference to the accumulator, which helps the performance when a large value type is used, e.g. collection. -
combinePreviousforSignalandSignalProducerno longer requires an initial value. The first tuple would be emitted as soon as the second value is received by the operator if no initial value is given. (#445, kudos to @andersio) -
New operator:
promoteValue. (#429) -
promoteErrorcan now infer the new error type from the context. (#413, kudos to @andersio) -
Property(initial:then:)gains overloads that accept a producer or signal of the wrapped value type when the value type is anOptional. (#396)
Swift 3.2+
-
In Swift 3.2 or later, you can use
map()with the new Smart Key Paths. (#435, kudos to @sharplet) -
In Swift 3.2 or later, you may create
BindingTargetfor a key path of a specific object. (#440, kudos to @andersio)
Deprecations and Removals
-
The requirement
BindingSource.observe(_:during:)and the implementations have been removed. -
All Swift 2 (ReactiveCocoa 4) obsolete symbols have been removed.
-
All deprecated methods and protocols in ReactiveSwift 1.1.x are no longer available.
Bugfixes
-
Fixed an impedance mismatch in the
Signalinternals that caused heap corruptions. (#449, kudos to @gparker42) -
Mitigated a race condition related to ARC in the
Signalinternal. (#456, kudos to @andersio)
Acknowledgement
Thank you to all of @ReactiveCocoa and all our contributors, but especially to @andersio, @calebd, @cwalcott, @eimantas, @erwald, @gparker42, @ikesyo, @Igor-Palaguta, @inamiy, @keitaito, @Marcocanc, @mdiep, @NachoSoto, @sharplet, @stephencelis, @stevebrambilla and @tjnet. ReactiveSwift is only possible due to the many hours of work that these individuals have volunteered. ❤️