ReactiveCocoa v5.0 Release Notes

  • Table of Contents

    1. Repository Split
    2. Swift 3.0 API Renaming
    3. New in 5.0: Cocoa Extensions
    4. Changes in ReactiveSwift 1.0
    5. 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:

    1. It’s independent of the Swift code
    2. It has a separate user base
    3. 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.

    1. 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(_:)))
      
    2. Object Lifetime

      Obtain a Lifetime token for any NSObject to observe their deinitialization.

      // Observe the lifetime of `object`.
      object.reactive.lifetime.ended.observeCompleted(doCleanup)
      
    3. Expressive, Safe Key Path Observation

      Establish key-value observations in the form of [SignalProducer][]s and strong-typed DynamicPropertys, 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
    ```
    
    1. 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, Signals 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. Signals 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 makes Signals much less dangerous.

    🚦 SignalProducer: buffer has been removed.

    🚦 Consider using Signal.pipe for buffer(0), MutableProperty for buffer(1) or replayLazily(upTo: n) for buffer(n).

    Properties: Composition

    🚦 Properties are now composable! They have many of the same operators as Signal and SignalProducer: 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 an inout. This enables the compiler to optimize it as an in-place mutation, which benefits collections, large structs and structs 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 type BindingTarget 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 wrapping Lifetime.Token deinitializes with the associated reference type. While it is provided as NSObject.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 Signals 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)