ReactiveCocoa v5.0 Release Notes
-
Table of Contents
- Repository Split
- Swift 3.0 API Renaming
- New in 5.0: Cocoa Extensions
- Changes in ReactiveSwift 1.0
- Migrating from the ReactiveObjC API
Repository Split
In version 5.0, we split ReactiveCocoa into multiple repositories for reasons explained in the sections below. The following should help you get started with choosing the repositories you require:
If you’re using only the Swift APIs, you can continue to include ReactiveCocoa. You will also need to link against ReactiveSwift, which is now a dependency of ReactiveCocoa.
If you’re using only the Objective-C APIs, you can switch to using ReactiveObjC. It has all the Obj-C code from RAC 2.
If you’re using both the Swift and Objective-C APIs, you likely require both ReactiveCocoa and ReactiveObjCBridge, which depend on ReactiveSwift and ReactiveObjC.
Attention: If you're using ReactiveCocoa, you'll most likely need to import ReactiveSwift as well when using classes or operators that are implemented in ReactiveSwift.
ReactiveCocoa
🏗 The ReactiveCocoa library is newly focused on Swift and the UI layers of Apple’s platforms, building on the work of Rex.
🏗 Reactive programming provides significant benefit in UI programming. RAC 3 and 4 focused on building out the new core Swift API. But we feel that those APIs have matured and it’s time for RAC-friendly extensions to AppKit and UIKit.
ReactiveSwift
The core, platform-independent Swift APIs have been extracted to a new framework, ReactiveSwift.
As Swift continues to grow as a language and a platform, we hope that it will expand beyond Cocoa and Apple’s platforms. Separating the Swift code makes it possible to use the reactive paradigm on other platforms.
ReactiveObjC
🚀 The 3.x and 4.x releases of ReactiveCocoa included the Objective-C code from ReactiveCocoa 2.x. That code has been moved to ReactiveObjC because:
- It’s independent of the Swift code
- It has a separate user base
- It has a separate group of maintainers
🚚 We hope that this move will enable continued support of ReactiveObjC.
ReactiveObjCBridge
Moving the Swift and Objective-C APIs to separate repositories meant that a new home was needed for the bridging layer between the two.
This bridge is an important tool for users that are working in mixed-language code bases. Whether you are slowly adding Swift to a mature product built with the ReactiveCocoa Objective-C APIs, or looking to adopt ReactiveCocoa in a mixed code base, the bridge is required to communicate between Swift and Objective-C code.
Swift 3.0 API Renaming
✅ We mostly adjusted the ReactiveCocoa API to follow the Swift 3 API Design Guidelines, or to match the Cocoa and Foundation API changes that came with Swift 3 and the latest platform SDKs.
Lots has changed, but if you're already migrating to Swift 3 then that should not come as a surprise. Fortunately for you, we've provided annotations in the source that should help you while using the Swift 3 migration tool that ships with Xcode 8. When changes aren't picked up by the migrator, they are often provided for you as Fix-Its.
Tip: You can apply all the suggested fix-its in the current scope by choosing Editor > Fix All In Scope from the main menu in Xcode, or by using the associated keyboard shortcut.
🆕 New in 5.0: Cocoa Extensions
Foundation: Object Interception
RAC 5.0 includes a few object interception tools from ReactiveObjC, remastered for ReactiveSwift.
Method Call Interception
Create signals that are sourced by intercepting Objective-C objects.
// Notify after every time `viewWillAppear(_:)` is called. let appearing = viewController.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:)))
Object Lifetime
Obtain a
Lifetime
token for anyNSObject
to observe their deinitialization.// Observe the lifetime of `object`. object.reactive.lifetime.ended.observeCompleted(doCleanup)
Expressive, Safe Key Path Observation
Establish key-value observations in the form of [
SignalProducer
][]s and strong-typedDynamicProperty
s, and enjoy the inherited composability.// A producer that sends the current value of `keyPath`, followed by // subsequent changes. // // Terminate the KVO observation if the lifetime of `self` ends. let producer = object.reactive.values(forKeyPath: #keyPath(key)) .take(during: self.reactive.lifetime) // A parameterized property that represents the supplied key path of the // wrapped object. It holds a weak reference to the wrapped object. let property = DynamicProperty<String>(object: person, keyPath: #keyPath(person.name))
These are accessible via the
reactive
magic property that is available on any ObjC objects.💻 AppKit & UIKit: UI bindings
💻 UI components now expose a collection of binding targets to which can be bound from any arbitrary streams of values.
💻 1. UI Bindings
UI components exposes [`BindingTarget`][]s, which accept bindings from any kind of streams of values via the `<~` operator. ```swift // Bind the `name` property of `person` to the text value of an `UILabel`. nameLabel.reactive.text <~ person.name ```
Controls and User Interactions
Interactive UI components expose [
Signal
][]s for control events and updates in the control value upon user interactions.A selected set of controls provide a convenience, expressive binding API for [
Action
][]s.// Update `allowsCookies` whenever the toggle is flipped. preferences.allowsCookies <~ toggle.reactive.isOnValues // Compute live character counts from the continuous stream of user initiated // changes in the text. textField.reactive.continuousTextValues.map { $0.characters.count } // Trigger `commit` whenever the button is pressed. button.reactive.pressed = CocoaAction(viewModel.commit)
These are accessible via the
reactive
magic property that is available on any ObjC objects.🔄 Changes in ReactiveSwift 1.0
🚦 Signal: Lifetime Semantics
🚦 Prior to RAC 5.0,
Signal
s lived and continued to emit values (and side effects) until they completed. This was very confusing, even for RAC veterans. So changes have been made to the lifetime semantics.Signal
s now live and continue to emit events only while either (a) they have observers or (b) they are retained. This clears up a number of unexpected cases and makesSignal
s much less dangerous.🚦 SignalProducer:
buffer
has been removed.🚦 Consider using
Signal.pipe
forbuffer(0)
,MutableProperty
forbuffer(1)
orreplayLazily(upTo: n)
forbuffer(n)
.Properties: Composition
🚦 Properties are now composable! They have many of the same operators as
Signal
andSignalProducer
:map
,filter
,combineLatest
,zip
,flatten
, etc.Properties: Lifetime Semantics
🚦 Composed properties, including those created via
Property(initial:then:)
, are semantically a view to their ultimate sources. In other words, the lifetime, the signal and the producer would respect the ultimate sources, and deinitialization of an instance of composed property would not have an effect on these.let property = MutableProperty(1) var composed: Property<Int> = property.map { $0 + 10 } composed.startWithValues { print("\($0)") } composed = nil property.value = 2 // The produced signal is still alive, printing `12` to the output stream.
Atomic: A more efficient
modify
⚡️
Atomic.modify
now passes its value to the supplied action as aninout
. This enables the compiler to optimize it as an in-place mutation, which benefits collections, largestruct
s andstruct
s with considerable amount of references.Moreover,
Atomic.modify
now returns the returned value from the supplied action, instead of the old value as in RAC 4.x, so as to reduce unnecessary copying.// ReactiveCocoa 4.0 let old = atomicCount.modify { $0 + 1 } // ReactiveSwift 1.0 let old = atomicCount.modify { value in let old = value value += 1 return old }
BindingTarget
The new
BindingTargetProtocol
protocol has been formally introduced to represent an entity to which can form a unidirectional binding using the<~
operator. A new typeBindingTarget
has also been introduced to represent non-observable targets that are expected to only be written to.// The `UIControl` exposes a `isEnabled` binding target. control.isEnabled <~ viewModel.isEnabled
Lifetime
🚦
Lifetime
is introduced to represent the lifetime of any arbitrary reference types. It works by completing the signal when its wrappingLifetime.Token
deinitializes with the associated reference type. While it is provided asNSObject.reactive.lifetime
on Objective-C objects, it can also be associated manually with Swift classes to provide the same semantics.public final class MyController { private let token = Lifetime.Token() public let lifetime: Lifetime public init() { lifetime = Lifetime(token) } }
Migrating from the ReactiveObjC API
Primitives
ReactiveObjC ReactiveCocoa 5.0 Cold RACSignal SignalProducer Hot RACSignal Signal Serial RACCommand Action Concurrent RACCommand Currently no counterpart.
Macros
ReactiveObjC ReactiveCocoa 5.0 RAC(label, text) Discover binding targets via .reactive on UI components. label.reactive.text <~ viewModel.name RACObserve(object, keyPath) NSObject.reactive.values(forKeyPath:)
NSObject interception
ReactiveObjC ReactiveCocoa 5.0 rac_willDeallocSignal NSObject.reactive.lifetime, in conjunction with the take(during:) operator. signal.take(during: object.reactive.lifetime) rac_liftSelector:withSignals: Apply combineLatest to your signals, and invoke the method in observeValues. 🚦 Signal.combineLatest(signal1, signal2) .take(during: self.reactive.lifetime) .observeValues { [weak self] in self?.perform(first: $0, second: $1) } rac_signalForSelector: NSObject.reactive.trigger(for:) and NSObject.reactive.signal(for:) rac_signalForSelector:fromProtocol: Currently no counterpart.
Control bindings and observations
ReactiveObjC ReactiveCocoa 5.0 Control value changes, e.g. textField.rac_textSignal() Discover control value
Signal
s via .reactive on UI components. viewModel.searchString <~ textField.reactive.textValues rac_signalForControlEvents: UIControl.reactive.trigger(for:) rac_command Discover action binding APIs via .reactive on UI components. button.reactive.pressed = CocoaAction(viewModel.submitAction)