Latest 4.1.0
Homepage https://github.com/ReactiveCocoa/ReactiveCocoa
License MIT
Platforms ios 8.0
Dependencies Result
Frameworks Foundation
Authors

Carthage compatible GitHub release Swift 2.2.x platforms

ReactiveCocoa (RAC) is a Cocoa framework inspired by Functional Reactive Programming. It provides APIs for composing and transforming streams of values over time.

  1. Introduction
  2. Example: online search
  3. Objective-C and Swift
  4. How does ReactiveCocoa relate to Rx?
  5. Getting started
  6. Playground

If you’re already familiar with functional reactive programming or what
ReactiveCocoa is about, check out the Documentation folder for more in-depth
information about how it all works. Then, dive straight into our documentation
comments
for learning more about individual APIs.

If you have a question, please see if any discussions in our GitHub
issues
or Stack
Overflow
have already
answered it. If not, please feel free to file your
own
!

Compatibility

This documents the RAC 4 which targets Swift 2.2.x. For Swift 1.2 support see RAC
3
.

Many thanks to Rheinfabrik for generously sponsoring the development of ReactiveCocoa 3!

Introduction

ReactiveCocoa is inspired by functional reactive
programming
.
Rather than using mutable variables which are replaced and modified in-place,
RAC offers “event streams,” represented by the Signal and
SignalProducer types, that send values over time.

Event streams unify all of Cocoa’s common patterns for asynchrony and event
handling, including:

Because all of these different mechanisms can be represented in the same way,
it’s easy to declaratively chain and combine them together, with less spaghetti
code and state to bridge the gap.

For more information about the concepts in ReactiveCocoa, see the [Framework
Overview][].

Example: online search

Let’s say you have a text field, and whenever the user types something into it,
you want to make a network request which searches for that query.

Observing text edits

The first step is to observe edits to the text field, using a RAC extension to
UITextField specifically for this purpose:

let searchStrings = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }

This gives us a signal producer which sends
values of type String. (The cast is currently
necessary
to bridge
this extension method from Objective-C.)

Making network requests

With each string, we want to execute a network request. Luckily, RAC offers an
NSURLSession extension for doing exactly that:

let searchResults = searchStrings
    .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in
        let URLRequest = self.searchRequestWithEscapedQuery(query)
        return NSURLSession.sharedSession().rac_dataWithRequest(URLRequest)
    }
    .map { (data, URLResponse) -> String in
        let string = String(data: data, encoding: NSUTF8StringEncoding)!
        return self.parseJSONResultsFromString(string)
    }
    .observeOn(UIScheduler())

This has transformed our producer of Strings into a producer of Arrays
containing the search results, which will be forwarded on the main thread
(thanks to the UIScheduler).

Additionally, flatMap(.Latest) here ensures that only one search—the
latest—is allowed to be running. If the user types another character while the
network request is still in flight, it will be cancelled before starting a new
one. Just think of how much code that would take to do by hand!

Receiving the results

This won’t actually execute yet, because producers must be started in order to
receive the results (which prevents doing work when the results are never used).
That’s easy enough:

searchResults.startWithNext { results in
    print("Search results: (results)")
}

Here, we watch for the Next event, which contains our results, and
just log them to the console. This could easily do something else instead, like
update a table view or a label on screen.

Handling failures

In this example so far, any network error will generate a Failed
event, which will terminate the event stream. Unfortunately, this
means that future queries won’t even be attempted.

To remedy this, we need to decide what to do with failures that occur. The
quickest solution would be to log them, then ignore them:

    .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in
        let URLRequest = self.searchRequestWithEscapedQuery(query)

        return NSURLSession.sharedSession()
            .rac_dataWithRequest(URLRequest)
            .flatMapError { error in
                print("Network error occurred: (error)")
                return SignalProducer.empty
            }
    }

By replacing failures with the empty event stream, we’re able to effectively
ignore them.

However, it’s probably more appropriate to retry at least a couple of times
before giving up. Conveniently, there’s a retry operator to do exactly that!

Our improved searchResults producer might look like this:

let searchResults = searchStrings
    .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in
        let URLRequest = self.searchRequestWithEscapedQuery(query)

        return NSURLSession.sharedSession()
            .rac_dataWithRequest(URLRequest)
            .retry(2)
            .flatMapError { error in
                print("Network error occurred: (error)")
                return SignalProducer.empty
            }
    }
    .map { (data, URLResponse) -> String in
        let string = String(data: data, encoding: NSUTF8StringEncoding)!
        return self.parseJSONResultsFromString(string)
    }
    .observeOn(UIScheduler())

Throttling requests

Now, let’s say you only want to actually perform the search when the user pauses
typing, to minimize traffic.

ReactiveCocoa has a declarative throttle operator that we can apply to our
search strings:

let searchStrings = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }
    .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler)

This prevents values from being sent less than 0.5 seconds apart, so the user
must stop editing for at least that long before we’ll use their search string.

To do this manually would require significant state, and end up much harder to
read! With ReactiveCocoa, we can use just one operator to incorporate time into
our event stream.

Debugging event streams

Due to its nature, a stream’s stack trace might have dozens of frames, which, more often than not, can make debugging a very frustrating activity.
A naive way of debugging, is by injecting side effects into the stream, like so:

let searchString = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }
    .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler)
    .on(event: { print ($0) }) // the side effect

This will print the stream’s events, while preserving the original stream behaviour. Both SignalProducer
and Signal provide the logEvents operator, that will do this automatically for you:

let searchString = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }
    .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler)
    .logEvents()

For more information and advance usage, check the Debugging Techniques document.

Objective-C and Swift

Although ReactiveCocoa was started as an Objective-C framework, as of version
3.0
, all major feature development is concentrated on the Swift API.

RAC’s Objective-C API and Swift API are entirely separate, but there is
a bridge to convert between the two. This
is mostly meant as a compatibility layer for older ReactiveCocoa projects, or to
use Cocoa extensions which haven’t been added to the Swift API yet.

The Objective-C API will continue to exist and be supported for the foreseeable
future, but it won’t receive many improvements. For more information about using
this API, please consult our legacy documentation.

We highly recommend that all new projects use the Swift API.

How does ReactiveCocoa relate to Rx?

ReactiveCocoa was originally inspired, and therefore heavily influenced, by
Microsoft’s Reactive
Extensions
(Rx) library. There are many ports of Rx, including RxSwift, but ReactiveCocoa is intentionally not a direct port.

Where RAC differs from Rx, it is usually to:

  • Create a simpler API
  • Address common sources of confusion
  • More closely match Cocoa conventions

The following are some of the concrete differences, along with their rationales.

Naming

In most versions of Rx, Streams over time are known as Observables, which
parallels the Enumerable type in .NET. Additionally, most operations in Rx.NET
borrow names from LINQ,
which uses terms reminiscent of relational databases, like Select and Where.

RAC is focused on matching Swift naming first and foremost, with terms like
map and filter instead. Other naming differences are typically inspired by
significantly better alternatives from Haskell or
Elm (which is the primary source for the “signal”
terminology).

Signals and Signal Producers (“hot” and “cold” observables)

One of the most confusing aspects of Rx is that of “hot”, “cold”, and “warm”
observables
(event streams).

In short, given just a method or function declaration like this, in C#:

IObservable<string> Search(string query)

… it is impossible to tell whether subscribing to (observing) that
IObservable will involve side effects. If it does involve side effects, it’s
also impossible to tell whether each subscription has a side effect, or if only
the first one does.

This example is contrived, but it demonstrates a real, pervasive problem
that makes it extremely hard to understand Rx code (and pre-3.0 ReactiveCocoa
code) at a glance.

ReactiveCocoa 3.0 has solved this problem by distinguishing side
effects with the separate Signal and SignalProducer types. Although this
means there’s another type to learn about, it improves code clarity and helps
communicates intent much better.

In other words, ReactiveCocoa’s changes here are simple, not
easy
.

Typed errors

When signals and signal producers are allowed to fail in ReactiveCocoa,
the kind of error must be specified in the type system. For example,
Signal<Int, NSError> is a signal of integer values that may fail with an error
of type NSError.

More importantly, RAC allows the special type NoError to be used instead,
which statically guarantees that an event stream is not allowed to send a
failure. This eliminates many bugs caused by unexpected failure events.

In Rx systems with types, event streams only specify the type of their
values—not the type of their errors—so this sort of guarantee is impossible.

UI programming

Rx is basically agnostic as to how it’s used. Although UI programming with Rx is
very common, it has few features tailored to that particular case.

RAC takes a lot of inspiration from ReactiveUI,
including the basis for Actions.

Unlike ReactiveUI, which unfortunately cannot directly change Rx to make it more
friendly for UI programming, ReactiveCocoa has been improved many times
specifically for this purpose
—even when it means diverging further from Rx.

Getting started

ReactiveCocoa supports OS X 10.9+, iOS 8.0+, watchOS 2.0, and tvOS 9.0.

To add RAC to your application:

  1. Add the ReactiveCocoa repository as a
    submodule of your
    application’s repository.
  2. Run script/bootstrap from within the ReactiveCocoa folder.
  3. Drag and drop ReactiveCocoa.xcodeproj and Carthage/Checkouts/Result/Result.xcodeproj
    into your application’s Xcode project or workspace.
  4. On the “General” tab of your application target’s settings, add
    ReactiveCocoa.framework and Result.framework to the “Embedded Binaries” section.
  5. If your application target does not contain Swift code at all, you should also
    set the EMBEDDED_CONTENT_CONTAINS_SWIFT build setting to “Yes”.

Or, if you’re using Carthage, simply add
ReactiveCocoa to your Cartfile:

github "ReactiveCocoa/ReactiveCocoa"

Make sure to add both ReactiveCocoa.framework and Result.framework to "Linked Frameworks and Libraries" and "copy-frameworks" Build Phases.

If you would prefer to use CocoaPods, there are some
unofficial podspecs
that have been generously contributed by third parties.

Once you’ve set up your project, check out the Framework Overview for
a tour of ReactiveCocoa’s concepts, and the Basic Operators for some
introductory examples of using it.

Playground

We also provide a great Playground, so you can get used to ReactiveCocoa’s operators. In order to start using it:

  1. Clone the ReactiveCocoa repository.
  2. Run script/bootstrap from within the ReactiveCocoa folder.
  3. Open ReactiveCocoa.xcworkspace.
  4. Build both Result-Mac and ReactiveCocoa-Mac targets.
  5. Open the ReactiveCocoa.playground
  6. Have fun!

Latest podspec

{
    "name": "ReactiveCocoaEx",
    "version": "4.1.0",
    "summary": "homemade ReactiveCocoa that only works for iOS and don't have deprecation warnings!",
    "description": "ReactiveCocoa (RAC) is an Objective-C framework for Functional Reactive Programming.nIt provides APIs for composing and transforming streams of values.",
    "homepage": "https://github.com/ReactiveCocoa/ReactiveCocoa",
    "license": {
        "type": "MIT",
        "file": "LICENSE.md"
    },
    "authors": {
        "Hai Feng Kao": "[email protected]"
    },
    "platforms": {
        "ios": "8.0"
    },
    "source": {
        "git": "https://github.com/haifengkao/ReactiveCocoaEx.git",
        "tag": "v4.1.0Ex"
    },
    "dependencies": {
        "Result": [
            "~> 2.0"
        ]
    },
    "frameworks": "Foundation",
    "default_subspecs": "UI",
    "prepare_command": "sed -i '' '[email protected]@"\1"@g' ReactiveCocoa/ReactiveCocoa.h",
    "subspecs": [
        {
            "name": "no-arc",
            "source_files": "ReactiveCocoa/Objective-C/RACObjCRuntime.{h,m}",
            "requires_arc": false
        },
        {
            "name": "Core",
            "source_files": "ReactiveCocoa/**/*.{d,h,m}",
            "exclude_files": [
                "ReactiveCocoa/**/*{RACObjCRuntime,AppKit,NSControl,NSText,NSTable}*"
            ],
            "header_dir": "ReactiveCocoa",
            "private_header_files": [
                "**/*Private.h",
                "**/*EXTRuntimeExtensions.h",
                "**/RACEmpty*.h"
            ],
            "dependencies": {
                "ReactiveCocoa/no-arc": []
            },
            "watchos": {
                "exclude_files": "**/NSURLConnection*",
                "pod_target_xcconfig": {
                    "GCC_PREPROCESSOR_DEFINITIONS": "DTRACE_PROBES_DISABLED=1"
                }
            }
        },
        {
            "name": "UI",
            "dependencies": {
                "ReactiveCocoa/Core": []
            },
            "ios": {
                "source_files": [],
                "frameworks": "UIKit"
            },
            "osx": {
                "source_files": [
                    "**/ReactiveCocoa.h",
                    "ReactiveCocoa/**/*{AppKit,NSControl,NSText,NSTable}*"
                ],
                "frameworks": "AppKit"
            },
            "tvos": {
                "source_files": [
                    "**/ReactiveCocoa.h",
                    "ReactiveCocoa/**/*{UIButton,UICollectionReusableView,UIControl,UIGestureRecognizer,UISegmentedControl,UITableViewCell,UITableViewHeaderFooterView,UIText}*"
                ]
            },
            "watchos": {
                "source_files": "**/ReactiveCocoa.h"
            }
        }
    ]
}

Pin It on Pinterest

Share This