Latest 0.4.5
Homepage https://github.com/ReactorKit/ReactorKit
License MIT
Platforms ios 8.0, osx 10.11, tvos 9.0, watchos 2.0
Dependencies RxSwift
Frameworks Foundation
Authors

ReactorKit

Swift
CocoaPods


Platform


Build Status


Codecov


CocoaDocs

ReactorKit is a framework for a reactive and unidirectional Swift application architecture. This repository introduces the basic concept of ReactorKit and describes how to build an application using ReactorKit.

You may want to see the Examples section first if you’d like to see the actual code. Visit the API Reference for code-level documentation.

Table of Contents

Basic Concept

ReactorKit is a combination of Flux and Reactive Programming. The user actions and the view states are delivered to each layer via observable streams. These streams are unidirectional: the view can only emit actions and the reactor can only emit states.

flow

Design Goal

  • Testability: The first purpose of ReactorKit is to separate the business logic from a view. This can make the code testable. A reactor doesn’t have any dependency to a view. Just test reactors.
  • Start Small: ReactorKit doesn’t require the whole application to follow a single architecture. ReactorKit can be adopted partially, for one or more specific views. You don’t need to rewrite everything to use ReactorKit on your existing project.
  • Less Typing: ReactorKit focuses on avoiding complicated code for a simple thing. ReactorKit requires less code compared to other architectures. Start simple and scale up.

View

A View displays data. A view controller and a cell are treated as a view. The view binds user inputs to the action stream and binds the view states to each UI component. There’s no business logic in a view layer. A view just defines how to map the action stream and the state stream.

To define a view, just have an existing class conform a protocol named View. Then your class will have a property named reactor automatically. This property is typically set outside of the view.

class ProfileViewController: UIViewController, View {
  var disposeBag = DisposeBag()
}

profileViewController.reactor = UserViewReactor() // inject reactor

When the reactor property has changed, bind(reactor:) gets called. Implement this method to define the bindings of an action stream and a state stream.

func bind(reactor: ProfileViewReactor) {
  // action (View -> Reactor)
  refreshButton.rx.tap.map { Reactor.Action.refresh }
    .bindTo(reactor.action)
    .addDisposableTo(self.disposeBag)

  // state (Reactor -> View)
  reactor.state.map { $0.isFollowing }
    .bindTo(followButton.rx.isSelected)
    .addDisposableTo(self.disposeBag)
}

Reactor

A Reactor is an UI-independent layer which manages the state of a view. The foremost role of a reactor is to separate control flow from a view. Every view has its corresponding reactor and delegates all logic to its reactor. A reactor has no dependency to a view, so it can be easily tested.

Conform to the Reactor protocol to define a reactor. This protocol requires three types to be defined: Action, Mutation and State. It also requires a property named initialState.

class ProfileViewReactor: Reactor {
  // represent user actions
  enum Action {
    case refreshFollowingStatus(Int)
    case follow(Int)
  }

  // represent state changes
  enum Mutation {
    case setFollowing(Bool)
  }

  // represents the current view state
  struct State {
    var isFollowing: Bool = false
  }

  let initialState: State = State()
}

An Action represents a user interaction and State represents a view state. Mutation is a bridge between Action and State. A reactor converts the action stream to the state stream in two steps: mutate() and reduce().

flow-reactor

mutate()

mutate() receives an Action and generates an Observable<Mutation>.

func mutate(action: Action) -> Observable<Mutation>

Every side effect, such as an async operation or API call, is performed in this method.

func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // receive an action
    return UserAPI.isFollowing(userID) // create an API stream
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // convert to Mutation stream
      }

  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

reduce()

reduce() generates a new State from a previous State and a Mutation.

func reduce(state: State, mutation: Mutation) -> State

This method is a pure function. It should just return a new State synchronously. Don’t perform any side effects in this function.

func reduce(state: State, mutation: Mutation) -> State {
  var state = state // create a copy of the old state
  switch mutation {
  case let .setFollowing(isFollowing):
    state.isFollowing = isFollowing // manipulate the state, creating a new state
    return state // return the new state
  }
}

transform()

transform() transforms each stream. There are three transform() functions:

func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

Implement these methods to transform and combine with other observable streams. For example, transform(mutation:) is the best place for combining a global event stream to a mutation stream.

These methods can be also used for debugging purposes:

func transform(action: Observable<Action>) -> Observable<Action> {
  return action.debug("action") // Use RxSwift's debug() operator
}

Advanced

Service

ReactorKit has a special layer named Service. A service layer does the actual business logic. A reactor is a middle layer between a view and a service which manages event streams. When a reactor receives an user action from a view, the reactor calls the service logic. The service makes a network request and sends the response back to the reactor. Then the reactor create a mutation stream with the service response.

Here is an example of service:

protocol UserServiceType {
  func user(id: Int) -> Observable<User>
  func follow(id: Int) -> Observable<Void>
}

final class UserService: Service, UserServiceType {
  func user(id: Int) -> Observable<User> {
    return foo()
  }

  func follow(id: Int) -> Observable<Void> {
    return bar()
  }
}

Conventions

ReactorKit suggests some conventions to write clean and concise code.

  • A reactor should have the ServiceProvider as the first argument of its initializer.

    class MyViewReactor {
      init(provider: ServiceProviderType)
    }
  • You must create a reactor outside of the view and pass it to the view’s reactor property.

    Bad

    class MyView: UIView, View {
      init() {
        self.reactor = MyViewReactor()
      }
    }

    Good

    let view = MyView()
    view.reactor = MyViewReactor(provider: provider)
  • The ServiceProvider should be created once and passed to the first-most View.

    let serviceProvider = ServiceProvider()
    let firstViewReactor = FirstViewReactor(provider: serviceProvider)
    window.rootViewController = FirstViewController(reactor: firstViewReactor)

Examples

  • Counter: The most simple and basic example of ReactorKit
  • GitHub Search: A simple application which provides a GitHub repository search
  • RxTodo: iOS Todo Application using ReactorKit
  • Cleverbot: iOS Messaging Application using Cleverbot and ReactorKit
  • Drrrible: Dribbble for iOS using ReactorKit (App Store)
  • Passcode: Passcode for iOS RxSwift, ReactorKit and IGListKit example

Dependencies

Requirements

  • Swift 3
  • iOS 8
  • macOS 10.11
  • tvOS 9.0
  • watchOS 2.0

Installation

Contribution

Any discussions and pull requests are welcomed 💖

  • To development:

    $ TEST=1 swift package generate-xcodeproj
  • To test:

    $ swift test

Community

Join #reactorkit on RxSwift Slack!

Changelog

  • 2017-04-18
    • Change the repository name to ReactorKit.
  • 2017-03-17
    • Change the architecture name from RxMVVM to The Reactive Architecture.
    • Every ViewModels are renamed to ViewReactors.

License

ReactorKit is under MIT license. See the LICENSE for more info.

Latest podspec

{
    "name": "ReactorKit",
    "version": "0.4.5",
    "summary": "A framework for reactive and unidirectional Swift application architecture",
    "homepage": "https://github.com/ReactorKit/ReactorKit",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "Suyeol Jeon": "[email protected]"
    },
    "source": {
        "git": "https://github.com/ReactorKit/ReactorKit.git",
        "tag": "0.4.5"
    },
    "source_files": "Sources/*.swift",
    "frameworks": "Foundation",
    "dependencies": {
        "RxSwift": [
            ">= 3.0.0"
        ]
    },
    "platforms": {
        "ios": "8.0",
        "osx": "10.11",
        "tvos": "9.0",
        "watchos": "2.0"
    },
    "pod_target_xcconfig": {
        "SWIFT_VERSION": "3.0"
    },
    "pushed_with_swift_version": "3.1"
}

Pin It on Pinterest

Share This