This release of ReactiveCocoa contains major breaking changes that we were unable to make after freezing the 1.0 API. All changes are focused on eliminating bad code and bad usage patterns, reducing confusion, and increasing flexibility in the framework.
For a complete list of changes in ReactiveCocoa 2.0, see the milestone.
- Simplified and safer KVO
- Safer commands with less state
- Fallback nil value for RAC macro
- Explicit schedulers for time-based operators
- More powerful selector signals
- Simpler two-way bindings
- Better bindings for AppKit
- More obvious sequencing operator
- Renamed signal binding method
- Consistent selector lifting
- Renamed scheduled signal constructors
- Notification immediately before object deallocation
- Extensible queue-based schedulers
- GCD time values replaced with NSDate
- Windows and numbered buffers removed
- NSTask extension removed
- RACSubscriber class now private
- Commands for UIButton
- Signal for UIActionSheet button clicks
- Better documentation for asynchronous backtraces
- Fixed libextobjc duplicated symbols
- Bindings for UIKit classes
- Signal subscription side effects
- Test scheduler
RACAble and RACAbleWithStart have been replaced with a single
RACObserve macro.
RACObserve always starts with the current value of the property, and will
notice the
deallocation of weak
properties (unlike vanilla KVO).
Unlike the previous macros, which only required one argument for key paths on
self, RACObserve always requires two arguments.
To update:
- Replace uses of
RACAbleWithStart(self.key)withRACObserve(self, key). - Replace uses of
RACAble(self.key)with[RACObserve(self, key) skip:1](if skipping the starting value is necessary).
RACCommand has been completely
refactored. It is no
longer a RACSignal, and the behavior of -addSignalBlock: has been moved to
the initializer, making the class almost entirely immutable.
Reflecting the most common use case, KVO-notifying properties have been changed
into signals instead. During the change, canExecute was also renamed to
enabled, which should make its purpose more obvious for binding to the UI.
In addition, changes to executing, errors, and enabled are now guaranteed
to fire on the main thread, so UI observers no longer run in the background
unexpectedly.
All together, these improvements should make RACCommand more composable and
less imperative, so it fits into the framework better.
To update:
- Move execution logic from
-addSignalBlock:or-subscribeNext:into thesignalBlockpassed to the initializer. - Instead of subscribing to the result of
-addSignalBlock:, subscribe toexecutionSignalsor the result of-execute:instead. - Replace uses of
RACAbleWithStart(command, executing)withcommand.executing. - Replace uses of
RACAbleWithStart(command, canExecute)withcommand.enabled. - Remove uses of
deliverOn:RACScheduler.mainThreadScheduleronRACCommandproperties, as they are now unnecessary.
The RAC macro now always
requires two or three
arguments:
- The object to bind to.
- The key path to set when new values are sent.
- (Optional) A value to set when
nilis sent on the signal.
This is necessary to avoid a -setNilValueForKey: exception when observing
a primitive property through an intermediate object which gets set to nil.
This is not actually a change in key-value observing behavior — it's a caveat
with KVO regardless — but RACObserve makes it more prominent, because the
deallocation of weak properties will be considered a change to nil.
To update:
- Replace uses of
RAC(self.objectProperty)withRAC(self, objectProperty). - When binding a signal that might send nil (like a key path observation) to
a primitive property, provide a default value:
RAC(self, integerProperty, @5)
-bufferWithTime:, +interval:, and -timeout: have been unintuitive and
error-prone because of their implicit use of a background scheduler.
All of the aforementioned methods now require a scheduler argument, so that it's clear how events should be delivered.
To update:
- To match the previous behavior exactly, pass in
[RACScheduler scheduler]. Note that this creates a new background scheduler for events to arrive upon. - If you were already using
-deliverOn:to force one of the above operators to deliver onto a specific scheduler, you can eliminate that hop and pass the scheduler into the operator directly.
-rac_signalForSelector: has been completely
refactored to support
listening for invocations of existing methods, new and existing methods with
multiple arguments, and existing methods with return values.
+rac_signalForSelector: (the class method variant) was removed, because
swizzling class methods is dangerous and difficult to do correctly.
To update:
- Most existing uses of
-rac_signalForSelector:shouldn't require any changes. However, thesuperimplementation (if available) of any targeted selector will now be invoked, where it wasn't previously. Verify that existing uses can handle this case. - Replace uses of
+rac_signalForSelector:by implementing the class method and sending arguments onto aRACSubjectinstead.
RACPropertySubject and RACBinding have been
replaced with
RACChannel and RACChannelTerminal. Similarly, RACObservablePropertySubject
has been replaced with RACKVOChannel.
In addition to slightly better terminology and more obvious usage, channels only offer two-way bindings by default, which is a simplification over the previous N-way binding interface.
Because of the sweeping conceptual changes, the old APIs have been completely removed without deprecation.
To update:
- Instead of creating a
RACPropertySubject, create aRACChannel. Replace N-way property subjects (where N is greater than 2) with multipleRACChannels. - Instead of creating a
RACObservablePropertySubject, create aRACKVOChannelor use theRACChannelTomacro. - Replace uses of
RACBindingwithRACChannelTerminal. - Replace uses of
RACBind(self.objectProperty)withRACChannelTo(self, objectProperty). Add a default value for primitive properties:RACChannelTo(self, integerProperty, @5) - Replace uses of
-bindTo:with the explicit subscription of two endpoints:
[binding1.followingEndpoint subscribe:binding2.leadingEndpoint];
[[binding2.leadingEndpoint skip:1] subscribe:binding1.followingEndpoint];-rac_bind:toObject:withKeyPath: and related methods have been
replaced with
-rac_channelToBinding:options:, which returns a RACChannelTerminal that can be used as
a two-way binding or a one-way signal.
To update:
- If possible, refactor code to use the new
RACChannelinterface. This bridges Cocoa Bindings with the full power of ReactiveCocoa. - For a direct conversion, use
-bind:toObject:withKeyPath:options:with the following options:@{ NSContinuouslyUpdatesValueBindingOption: @YES }for-rac_bind:toObject:withKeyPath:@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSNullPlaceholderBindingOption: nilValue }for-rac_bind:toObject:withKeyPath:nilValue:@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSValueTransformerBindingOption: valueTransformer }for-rac_bind:toObject:withKeyPath:transform:@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSValueTransformerBindingOption: NSNegateBooleanTransformerName }for-rac_bind:toObject:withNegatedKeyPath:
To make the sequencing and transformation operators less confusing,
-sequenceMany: has been removed, and -sequenceNext: has been
renamed to -then:.
To update:
- Replace uses of
-sequenceMany:with-flattenMap:and a block that doesn't use its argument. - Replace uses of
-sequenceNext:with-then:.
-toProperty:onObject: and -[NSObject rac_deriveProperty:from:] have been
combined into a new
-[RACSignal setKeyPath:onObject:nilValue:] method.
The nilValue parameter was added in parallel with the
RAC macro, but the semantics are otherwise
identical.
To update:
- Replace
-toProperty:onObject:and-rac_deriveProperty:from:with-setKeyPath:onObject:. - When binding a signal that might send nil (like a key path observation) to
a primitive property, provide a default value:
[signal setKeyPath:@"integerProperty" onObject:self nilValue:@5]
In the interests of parametricity,
-rac_liftSelector:withObjects: has been
replaced with
-rac_liftSelector:withSignals:, which requires all arguments to be signals.
This makes usage more consistent.
-rac_liftBlock:withArguments: has been removed, because it's redundant with
RACSignal operators.
The -rac_lift proxy has also been removed, because there's no way to make it
consistent in the
same way.
To update:
- Wrap non-signal arguments with
+[RACSignal return:]and add a nil terminator. - Replace block lifting with
+combineLatest:reduce:. - Replace uses of
-rac_liftwith-rac_liftSelector:withSignals:.
+start:, +startWithScheduler:block, and +startWithScheduler:subjectBlock:
have been combined into a single
+startEagerlyWithScheduler:block:
constructor.
The key improvements here are a more intuitive name, a required RACScheduler
to make it clear where the block is executed, and use of <RACSubscriber>
instead of RACSubject to make it more obvious how to use the block argument.
To update:
- Use
[RACScheduler scheduler]to match the previous implicit scheduling behavior of+start:. - Refactor blocks that return values and set
success/error, to send events to the given<RACSubscriber>instead.
-rac_didDeallocSignal has been
removed in favor of
-rac_willDeallocSignal,
because most teardown should happen before the object becomes invalid.
-rac_addDeallocDisposable: has also been
removed in favor of
using the object's rac_deallocDisposable directly.
To update:
- Replace uses of
-rac_didDeallocSignalwithrac_willDeallocSignal. - Replace uses of
-rac_addDeallocDisposable:by invoking-addDisposable:on the object'srac_deallocDisposableinstead.
RACQueueScheduler has been exposed as a public
class, so consumers can create
their own scheduler implementations using GCD queues.
The
RACTargetQueueScheduler
subclass replaces the +schedulerWithQueue:name: method.
To update:
Replace uses of +schedulerWithQueue:name: with -[RACTargetQueueScheduler initWithName:targetQueue:].
NSDate now replaces
dispatch_time_t values in RACScheduler, because dates are easier to use, more
convertible to other formats, and can be used to implement a virtualized time
scheduler.
To update:
Replace dispatch_time_t calculations with NSDate.
-windowWithStart:close: and -buffer: have been
removed because
they're not well-tested, and their functionality can be achieved with other
operators.
-bufferWithTime: is still supported.
To update:
- Refactor uses of
-windowWithStart:close:with different patterns. - Replace uses of
-buffer:with take, collect, and repeat.
NSTask+RACSupport has been
removed, because it
was buggy and unsupported.
To update:
Use a vanilla NSTask, and send events onto RACSubjects instead.
The RACSubscriber class (not to be confused with the protocol) should never be
used directly, so it has been
hidden.
To update:
Replace uses of RACSubscriber with id<RACSubscriber> or RACSubject.
UIButton now has a rac_command
property.
Any command set will be executed when the button is tapped, and the button will be disabled whenever the command is unable to execute.
UIActionSheet now has a rac_buttonClickedSignal
property, which will
fire whenever one of the sheet's buttons is clicked.
Documentation has been
added to
RACBacktrace explaining how to start collecting backtraces and print them out
in the debugger.
The +captureBacktrace… methods have been renamed to +backtrace…, and
+printBacktrace has been removed in favor of using po [RACBacktrace backtrace]
from the debugger.
The rac_ GCD functions which collect backtraces have also been exposed,
primarily for use on iOS.
If libextobjc was used in a project that statically linked ReactiveCocoa, duplicate symbol errors could result.
To avoid this issue, RAC now renames the libextobjc symbols that it uses.
RACChannel interfaces have been added to many UIKit classes, greatly simplifying glue code between your models and views.
For example, assuming you want to bind a person model's name property:
UITextField *nameField = ...;
RACChannelTerminal *nameTerminal = RACChannelTo(model, name);
RACChannelTerminal *nameFieldTerminal = [nameField rac_newTextChannel];
[nameTerminal subscribe:nameFieldTerminal];
[nameFieldTerminal subscribe:nameTerminal];You may also bind multiple controls to the same property, for example a UISlider for coarse editing and a UIStepper for fine-grained editing.
RACSignal now has the
-initially:
operator, which executes a given block each time the signal is subscribed to.
This is symmetric to -finally:.
RACTestScheduler is a new kind of scheduler that
virtualizes time. Enqueued blocks can be stepped through at any pace, no matter
how far in the future they're scheduled for, making it easy to test time-based
behavior without actually waiting in unit tests.