[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7075":3},{"id":4,"name":5,"fullName":6,"owner":5,"repo":5,"description":7,"homepage":8,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":21,"topics":22,"createdAt":9,"pushedAt":9,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":15,"starSnapshotCount":15,"syncStatus":32,"lastSyncTime":33,"discoverSource":34},7075,"ReactorKit","ReactorKit\u002FReactorKit","A library for reactive and unidirectional Swift applications","http:\u002F\u002Freactorkit.io",null,"Swift",2794,271,58,35,0,5,29.3,"MIT License",false,"main",true,[23,24,25,26,27,28],"architecture","reactive","reactorkit","rxswift","swift","unidirectional","2026-06-12 02:01:34","\u003Cimg alt=\"ReactorKit\" src=\"https:\u002F\u002Fcloud.githubusercontent.com\u002Fassets\u002F931655\u002F25277625\u002F6aa05998-26da-11e7-9b85-e48bec938a6e.png\" style=\"max-width: 100%\">\n\n\u003Cp align=\"center\">\n  \u003Cimg alt=\"Swift\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FSwift-6.0-orange.svg\">\n  \u003Ca href=\"https:\u002F\u002Fcocoapods.org\u002Fpods\u002FReactorKit\" target=\"_blank\">\n    \u003Cimg alt=\"CocoaPods\" src=\"http:\u002F\u002Fimg.shields.io\u002Fcocoapods\u002Fv\u002FReactorKit.svg\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\" target=\"_blank\">\n    \u003Cimg alt=\"Platform\" src=\"https:\u002F\u002Fimg.shields.io\u002Fcocoapods\u002Fp\u002FReactorKit.svg?style=flat\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Factions\" target=\"_blank\">\n    \u003Cimg alt=\"CI\" src=\"https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Fworkflows\u002FCI\u002Fbadge.svg\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fcodecov.io\u002Fgh\u002FReactorKit\u002FReactorKit\u002F\" target=\"_blank\">\n    \u003Cimg alt=\"Codecov\" src=\"https:\u002F\u002Fimg.shields.io\u002Fcodecov\u002Fc\u002Fgithub\u002FReactorKit\u002FReactorKit.svg\">\n  \u003C\u002Fa>\n\u003C\u002Fp>\n\nReactorKit 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.\n\nYou may want to see the [Examples](#examples) section first if you'd like to see the actual code. For an overview of ReactorKit's features and the reasoning behind its creation, you may also check the slides from this introductory presentation over at [SlideShare](https:\u002F\u002Fwww.slideshare.net\u002Fdevxoul\u002Fhello-reactorkit).\n\n## Table of Contents\n\n- [Table of Contents](#table-of-contents)\n- [Basic Concept](#basic-concept)\n  - [Design Goal](#design-goal)\n  - [View](#view)\n    - [Storyboard Support](#storyboard-support)\n  - [Reactor](#reactor)\n    - [`mutate()`](#mutate)\n    - [`reduce()`](#reduce)\n    - [`transform()`](#transform)\n- [Advanced](#advanced)\n  - [Global States](#global-states)\n  - [View Communication](#view-communication)\n  - [Testing](#testing)\n    - [What to test](#what-to-test)\n    - [View testing](#view-testing)\n    - [Reactor testing](#reactor-testing)\n  - [Threading](#threading)\n  - [Pulse](#pulse)\n- [SwiftUI](#swiftui)\n  - [ObservableState](#observablestate)\n  - [ObservedReactor](#observedreactor)\n  - [ReactorObserving](#reactorobserving)\n  - [Bindings](#bindings)\n    - [@ReactorBindable](#reactorbindable)\n    - [binding(get:send:)](#bindinggetsend)\n  - [ObservableStateIgnored](#observablestateignored)\n  - [Testing in SwiftUI](#testing-in-swiftui)\n- [Examples](#examples)\n- [Dependencies](#dependencies)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Contribution](#contribution)\n- [Community](#community)\n  - [Join](#join)\n  - [Community Projects](#community-projects)\n- [Who's using ReactorKit](#whos-using-reactorkit)\n- [Changelog](#changelog)\n- [License](#license)\n\n## Basic Concept\n\nReactorKit is a combination of [Flux](https:\u002F\u002Ffacebook.github.io\u002Fflux\u002F) and [Reactive Programming](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FReactive_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.\n\n\u003Cp align=\"center\">\n  \u003Cimg alt=\"flow\" src=\"https:\u002F\u002Fcloud.githubusercontent.com\u002Fassets\u002F931655\u002F25073432\u002Fa91c1688-2321-11e7-8f04-bf91031a09dd.png\" width=\"600\">\n\u003C\u002Fp>\n\n### Design Goal\n\n- **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 and test view bindings. See [Testing](#testing) section for details.\n- **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.\n- **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.\n\n### View\n\nA _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.\n\nTo define a view, just have an existing class conform a protocol named `ReactorView`. Then your class will have a property named `reactor` automatically. This property is typically set outside of the view.\n\n```swift\nclass ProfileViewController: UIViewController, ReactorView {\n  var disposeBag = DisposeBag()\n}\n\nprofileViewController.reactor = ProfileViewReactor() \u002F\u002F inject reactor\n```\n\nWhen the `reactor` property has changed, `bind(reactor:)` gets called. Implement this method to define the bindings of an action stream and a state stream.\n\n```swift\nfunc bind(reactor: ProfileViewReactor) {\n  \u002F\u002F action (View -> Reactor)\n  refreshButton.rx.tap.map { Reactor.Action.refresh }\n    .bind(to: reactor.action)\n    .disposed(by: self.disposeBag)\n\n  \u002F\u002F state (Reactor -> View)\n  reactor.state.map { $0.isFollowing }\n    .bind(to: followButton.rx.isSelected)\n    .disposed(by: self.disposeBag)\n}\n```\n\n#### Storyboard Support\n\nUse `DeferredReactorView` protocol if you're using a storyboard to initialize view controllers. Everything is same but the only difference is that the `DeferredReactorView` defers binding until the view is loaded.\n\n```swift\nlet viewController = MyViewController()\nviewController.reactor = MyViewReactor() \u002F\u002F will not execute `bind(reactor:)` immediately\n\nclass MyViewController: UIViewController, DeferredReactorView {\n  func bind(reactor: MyViewReactor) {\n    \u002F\u002F this is called after the view is loaded (viewDidLoad)\n  }\n}\n```\n\n### Reactor\n\nA _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.\n\nConform 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`.\n\n```swift\nclass ProfileViewReactor: Reactor {\n  \u002F\u002F represent user actions\n  enum Action {\n    case refreshFollowingStatus(Int)\n    case follow(Int)\n  }\n\n  \u002F\u002F represent state changes\n  enum Mutation {\n    case setFollowing(Bool)\n  }\n\n  \u002F\u002F represents the current view state\n  struct State {\n    var isFollowing: Bool = false\n  }\n\n  let initialState: State = State()\n}\n```\n\nAn `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()`.\n\n\u003Cp align=\"center\">\n  \u003Cimg alt=\"flow-reactor\" src=\"https:\u002F\u002Fcloud.githubusercontent.com\u002Fassets\u002F931655\u002F25098066\u002F2de21a28-23e2-11e7-8a41-d33d199dd951.png\" width=\"800\">\n\u003C\u002Fp>\n\n#### `mutate()`\n\n`mutate()` receives an `Action` and generates an `Observable\u003CMutation>`.\n\n```swift\nfunc mutate(action: Action) -> Observable\u003CMutation>\n```\n\nEvery side effect, such as an async operation or API call, is performed in this method.\n\n```swift\nfunc mutate(action: Action) -> Observable\u003CMutation> {\n  switch action {\n  case let .refreshFollowingStatus(userID): \u002F\u002F receive an action\n    return UserAPI.isFollowing(userID) \u002F\u002F create an API stream\n      .map { (isFollowing: Bool) -> Mutation in\n        return Mutation.setFollowing(isFollowing) \u002F\u002F convert to Mutation stream\n      }\n\n  case let .follow(userID):\n    return UserAPI.follow()\n      .map { _ -> Mutation in\n        return Mutation.setFollowing(true)\n      }\n  }\n}\n```\n\n#### `reduce()`\n\n`reduce()` generates a new `State` from a previous `State` and a `Mutation`.\n\n```swift\nfunc reduce(state: State, mutation: Mutation) -> State\n```\n\nThis method is a pure function. It should just return a new `State` synchronously. Don't perform any side effects in this function.\n\n```swift\nfunc reduce(state: State, mutation: Mutation) -> State {\n  var state = state \u002F\u002F create a copy of the old state\n  switch mutation {\n  case let .setFollowing(isFollowing):\n    state.isFollowing = isFollowing \u002F\u002F manipulate the state, creating a new state\n    return state \u002F\u002F return the new state\n  }\n}\n```\n\n#### `transform()`\n\n`transform()` transforms each stream. There are three `transform()` functions:\n\n```swift\nfunc transform(action: Observable\u003CAction>) -> Observable\u003CAction>\nfunc transform(mutation: Observable\u003CMutation>) -> Observable\u003CMutation>\nfunc transform(state: Observable\u003CState>) -> Observable\u003CState>\n```\n\nImplement 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. See the [Global States](#global-states) section for details.\n\nThese methods can be also used for debugging purposes:\n\n```swift\nfunc transform(action: Observable\u003CAction>) -> Observable\u003CAction> {\n  return action.debug(\"action\") \u002F\u002F Use RxSwift's debug() operator\n}\n```\n\n## Advanced\n\n### Global States\n\nUnlike Redux, ReactorKit doesn't define a global app state. It means that you can use anything to manage a global state. You can use a `BehaviorSubject`, a `PublishSubject` or even a reactor. ReactorKit doesn't force to have a global state so you can use ReactorKit in a specific feature in your application.\n\nThere is no global state in the **Action → Mutation → State** flow. You should use `transform(mutation:)` to transform the global state to a mutation. Let's assume that we have a global `BehaviorSubject` which stores the current authenticated user. If you'd like to emit a `Mutation.setUser(User?)` when the `currentUser` is changed, you can do as following:\n\n```swift\nvar currentUser: BehaviorSubject\u003CUser> \u002F\u002F global state\n\nfunc transform(mutation: Observable\u003CMutation>) -> Observable\u003CMutation> {\n  return Observable.merge(mutation, currentUser.map(Mutation.setUser))\n}\n```\n\nThen the mutation will be emitted each time the view sends an action to a reactor and the `currentUser` is changed.\n\n### View Communication\n\nYou must be familiar with callback closures or delegate patterns for communicating between multiple views. ReactorKit recommends you to use [reactive extensions](https:\u002F\u002Fgithub.com\u002FReactiveX\u002FRxSwift\u002Fblob\u002Fmaster\u002FRxSwift\u002FReactive.swift) for it. The most common example of `ControlEvent` is `UIButton.rx.tap`. The key concept is to treat your custom views as UIButton or UILabel.\n\n\u003Cp align=\"center\">\n  \u003Cimg alt=\"view-view\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F27789114-393e2eea-6026-11e7-9b32-bae314e672ee.png\" width=\"600\">\n\u003C\u002Fp>\n\nLet's assume that we have a `ChatViewController` which displays messages. The `ChatViewController` owns a `MessageInputView`. When an user taps the send button on the `MessageInputView`, the text will be sent to the `ChatViewController` and `ChatViewController` will bind in to the reactor's action. This is an example `MessageInputView`'s reactive extension:\n\n```swift\nextension Reactive where Base: MessageInputView {\n  var sendButtonTap: ControlEvent\u003CString> {\n    let source = base.sendButton.rx.tap.withLatestFrom(...)\n    return ControlEvent(events: source)\n  }\n}\n```\n\nYou can use that extension in the `ChatViewController`. For example:\n\n```swift\nmessageInputView.rx.sendButtonTap\n  .map(Reactor.Action.send)\n  .bind(to: reactor.action)\n```\n\n### Testing\n\nReactorKit has a built-in functionality for a testing. You'll be able to easily test both a view and a reactor with a following instruction.\n\n#### What to test\n\nFirst of all, you have to decide what to test. There are two things to test: a view and a reactor.\n\n- View\n  - Action: is a proper action sent to a reactor with a given user interaction?\n  - State: is a view property set properly with a following state?\n- Reactor\n  - State: is a state changed properly with an action?\n\n#### View testing\n\nA view can be tested with a _stub_ reactor. A reactor has a property `stub` which can log actions and force change states. If a reactor's stub is enabled, both `mutate()` and `reduce()` are not executed. A stub has these properties:\n\n```swift\nvar state: StateRelay\u003CReactor.State> { get }\nvar action: ActionSubject\u003CReactor.Action> { get }\nvar actions: [Reactor.Action] { get } \u002F\u002F recorded actions\n```\n\nHere are some example test cases:\n\n```swift\nfunc testAction_refresh() {\n  \u002F\u002F 1. prepare a stub reactor\n  let reactor = MyReactor()\n  reactor.isStubEnabled = true\n\n  \u002F\u002F 2. prepare a view with a stub reactor\n  let view = MyView()\n  view.reactor = reactor\n\n  \u002F\u002F 3. send an user interaction programmatically\n  view.refreshControl.sendActions(for: .valueChanged)\n\n  \u002F\u002F 4. assert actions\n  XCTAssertEqual(reactor.stub.actions.last, .refresh)\n}\n\nfunc testState_isLoading() {\n  \u002F\u002F 1. prepare a stub reactor\n  let reactor = MyReactor()\n  reactor.isStubEnabled = true\n\n  \u002F\u002F 2. prepare a view with a stub reactor\n  let view = MyView()\n  view.reactor = reactor\n\n  \u002F\u002F 3. set a stub state\n  reactor.stub.state.value = MyReactor.State(isLoading: true)\n\n  \u002F\u002F 4. assert view properties\n  XCTAssertEqual(view.activityIndicator.isAnimating, true)\n}\n```\n\n#### Reactor testing\n\nA reactor can be tested independently.\n\n```swift\nfunc testIsBookmarked() {\n  let reactor = MyReactor()\n  reactor.action.onNext(.toggleBookmarked)\n  XCTAssertEqual(reactor.currentState.isBookmarked, true)\n  reactor.action.onNext(.toggleBookmarked)\n  XCTAssertEqual(reactor.currentState.isBookmarked, false)\n}\n```\n\nSometimes a state is changed more than one time for a single action. For example, a `.refresh` action sets `state.isLoading` to `true` at first and sets to `false` after the refreshing. In this case it's difficult to test `state.isLoading` with `currentState` so you might need to use [RxTest](https:\u002F\u002Fgithub.com\u002FReactiveX\u002FRxSwift) or [RxExpect](https:\u002F\u002Fgithub.com\u002Fdevxoul\u002FRxExpect). Here is an example test case using RxSwift:\n\n```swift\nfunc testIsLoading() {\n  \u002F\u002F given\n  let scheduler = TestScheduler(initialClock: 0)\n  let reactor = MyReactor()\n  let disposeBag = DisposeBag()\n\n  \u002F\u002F when\n  scheduler\n    .createHotObservable([\n      .next(100, .refresh) \u002F\u002F send .refresh at 100 scheduler time\n    ])\n    .subscribe(reactor.action)\n    .disposed(by: disposeBag)\n\n  \u002F\u002F then\n  let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {\n    reactor.state.map(\\.isLoading)\n  }\n  XCTAssertEqual(response.events.map(\\.value.element), [\n    false, \u002F\u002F initial state\n    true,  \u002F\u002F just after .refresh\n    false  \u002F\u002F after refreshing\n  ])\n}\n```\n\n### Threading\n\nReactorKit does not move work between threads on your behalf. `transform(action:)`, `mutate(_:)`, `transform(mutation:)`, `reduce(_:_:)`, `transform(state:)`, and state subscribers all run on whatever thread the upstream emits on.\n\n| Guarantee | Owner |\n|---|---|\n| Same-thread reentrant action serialization | ReactorKit |\n| Main-thread action dispatch | You (DEBUG warning on violation) |\n| `mutate` background → main hop | You (`.observe(on:)` inside `mutate`) |\n| SwiftUI main-thread state delivery | ReactorKit (`ObservedReactor`) |\n\n**Action dispatch contract**: callers of `action.onNext(_:)` are expected to dispatch from the main thread. This keeps action entry into the pipeline serialized. The same rule applies to any external stream merged inside `transform(action:)` — apply `.observe(on: MainScheduler.instance)` to it before merging. In DEBUG builds an `os_log` fault is emitted when an action reaches `mutate(_:)` off the main thread.\n\n**Reentrant actions**: an action dispatched from inside `reduce(_:_:)` or a state subscriber is queued on the current thread's trampoline and processed after the in-flight emission completes. Without this, the reentrant action's `reduce` could interrupt the outer emission and reorder state changes — e.g. a `dismiss` followed by a `present` from the dismiss handler.\n\nIf `mutate(_:)` returns an observable that emits from a background thread (e.g. a network response), apply `.observe(on:)` explicitly to hop back onto a thread compatible with your consumers:\n\n```swift\nfunc mutate(action: Action) -> Observable\u003CMutation> {\n  switch action {\n  case .load:\n    return api.fetch()\n      .map(Mutation.setItems)\n      .observe(on: MainScheduler.instance)\n  }\n}\n```\n\nThe same applies when `transform(mutation:)` merges an external stream — the merged source bypasses the action-dispatch boundary, so make sure its emissions land on the same thread your other mutations do.\n\n`reduce(_:_:)` is expected to be single-writer. ReactorKit does not enforce this — if you fan mutations in from multiple threads, `currentState` can race. Keep mutation emissions on a single thread.\n\n`reactor.state` emits on whichever thread produced the final mutation. When binding directly to UI outside of `ObservedReactor`, add `.observe(on: MainScheduler.instance)` at the call site if any of your mutations may emit from a background thread. `ObservedReactor` already hops state onto the main thread before writing, so SwiftUI users are unaffected.\n\n### Pulse\n\n`Pulse` has diff only when mutated\nTo explain in code, the results are as follows.\n\n```swift\nvar messagePulse: Pulse\u003CString?> = Pulse(wrappedValue: \"Hello tokijh\")\n\nlet oldMessagePulse: Pulse\u003CString?> = messagePulse\nmessagePulse.value = \"Hello tokijh\" \u002F\u002F add valueUpdatedCount +1\n\noldMessagePulse.valueUpdatedCount != messagePulse.valueUpdatedCount \u002F\u002F true\noldMessagePulse.value == messagePulse.value \u002F\u002F true\n```\n\nUse when you want to receive an event only if the new value is assigned, even if it is the same value.\nlike `alertMessage` (See follows or [PulseTests.swift](https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Fblob\u002Fmain\u002FTests\u002FReactorKitTests\u002FPulseTests.swift))\n\n```swift\n\u002F\u002F Reactor\nprivate final class MyReactor: Reactor {\n  struct State {\n    @Pulse var alertMessage: String?\n  }\n\n  func mutate(action: Action) -> Observable\u003CMutation> {\n    switch action {\n    case let .alert(message):\n      return Observable.just(Mutation.setAlertMessage(message))\n    }\n  }\n\n  func reduce(state: State, mutation: Mutation) -> State {\n    var newState = state\n\n    switch mutation {\n    case let .setAlertMessage(alertMessage):\n      newState.alertMessage = alertMessage\n    }\n\n    return newState\n  }\n}\n\n\u002F\u002F View\nreactor.pulse(\\.$alertMessage)\n  .compactMap { $0 } \u002F\u002F filter nil\n  .subscribe(onNext: { [weak self] (message: String) in\n    self?.showAlert(message)\n  })\n  .disposed(by: disposeBag)\n\n\u002F\u002F Cases\nreactor.action.onNext(.alert(\"Hello\"))  \u002F\u002F showAlert() is called with `Hello`\nreactor.action.onNext(.alert(\"Hello\"))  \u002F\u002F showAlert() is called with `Hello`\nreactor.action.onNext(.doSomeAction)    \u002F\u002F showAlert() is not called\nreactor.action.onNext(.alert(\"Hello\"))  \u002F\u002F showAlert() is called with `Hello`\nreactor.action.onNext(.alert(\"tokijh\")) \u002F\u002F showAlert() is called with `tokijh`\nreactor.action.onNext(.doSomeAction)    \u002F\u002F showAlert() is not called\n```\n\n## SwiftUI\n\nReactorKit supports SwiftUI through the `ReactorKitSwiftUI` module. You can use your existing reactors in SwiftUI views with one addition: annotate your `State` with `@ObservableState`.\n\n### ObservableState\n\nAdd the `@ObservableState` macro to your reactor's `State` struct. This enables per-property observation — SwiftUI re-renders only when the properties a view actually reads have changed.\n\n```swift\nimport ReactorKitObservation\n\nfinal class ProfileViewReactor: Reactor {\n  enum Action {\n    case follow\n  }\n\n  enum Mutation {\n    case setFollowing(Bool)\n  }\n\n  @ObservableState\n  struct State {\n    var username: String = \"\"\n    var isFollowing: Bool = false\n  }\n\n  let initialState = State()\n}\n```\n\n### ObservedReactor\n\nWrap your reactor in `ObservedReactor` to make it observable by SwiftUI. Access state properties directly via dynamic member lookup.\n\n```swift\nimport ReactorKitSwiftUI\n\nstruct ProfileView: View {\n  let reactor: ObservedReactor\u003CProfileViewReactor>\n\n  var body: some View {\n    ReactorObserving {\n      Text(reactor.username)\n      Button(\"Follow\") { reactor.send(.follow) }\n    }\n  }\n}\n```\n\n### ReactorObserving\n\n`ReactorObserving` enables observation tracking on iOS 13–16. On iOS 17+, SwiftUI natively tracks `Observable` property access, so `ReactorObserving` becomes a transparent passthrough.\n\n```swift\n\u002F\u002F iOS 17+: ReactorObserving is optional\nstruct ProfileView: View {\n  let reactor: ObservedReactor\u003CProfileViewReactor>\n\n  var body: some View {\n    Text(reactor.username)\n    Button(\"Follow\") { reactor.send(.follow) }\n  }\n}\n```\n\n### Bindings\n\n#### @ReactorBindable\n\nFor two-way bindings, conform your `Action` to `BindableAction` and use `@ReactorBindable`:\n\n```swift\n\u002F\u002F Reactor\nenum Action: BindableAction {\n  case binding(BindingAction\u003CState>)\n  \u002F\u002F other actions...\n}\n\n\u002F\u002F In reduce:\ncase .binding(let action):\n  action.apply(to: &state)\n```\n\n```swift\n\u002F\u002F View\nstruct ProfileView: View {\n  @ReactorBindable var reactor: ObservedReactor\u003CProfileViewReactor>\n\n  var body: some View {\n    ReactorObserving {\n      TextField(\"Name\", text: $reactor.name)\n      Toggle(\"Enabled\", isOn: $reactor.isEnabled)\n    }\n  }\n}\n```\n\n#### binding(get:send:)\n\nFor bindings that map to custom actions (not `BindableAction`), use `binding(get:send:)`:\n\n```swift\n\u002F\u002F Closure form\nTextField(\"Name\", text: reactor.binding(\n  get: \\.name,\n  send: { .setName($0) }\n))\n\n\u002F\u002F Constant action form\nToggle(\"Enabled\", isOn: reactor.binding(\n  get: \\.isEnabled,\n  send: .toggleEnabled\n))\n```\n\n> **Note**: `binding(get:send:)` produces closure-based bindings. SwiftUI may re-evaluate child views on every parent body evaluation even when the bound value hasn't changed. Prefer `@ReactorBindable` when using `BindableAction` — it produces location-based bindings that let SwiftUI skip unnecessary re-renders.\n\n### ObservableStateIgnored\n\nProperties with property wrappers (other than `@Pulse`) must be explicitly excluded from observation tracking with `@ObservableStateIgnored`:\n\n```swift\n@ObservableState\nstruct State {\n  var count: Int = 0\n  @ObservableStateIgnored\n  @MyCustomWrapper var customValue: String = \"\"\n}\n```\n\n`@Pulse` is handled automatically — no `@ObservableStateIgnored` needed.\n\n### Testing in SwiftUI\n\nReactorKit's built-in stub works with `ObservedReactor`. Use it in SwiftUI Previews:\n\n```swift\n#Preview {\n  let reactor = ProfileViewReactor()\n  reactor.isStubEnabled = true\n  reactor.stub.state.value = .init(username: \"devxoul\")\n  return ProfileView(reactor: ObservedReactor(reactor: reactor))\n}\n```\n\n### SwiftUI Installation\n\nSwiftUI support requires Swift Package Manager (CocoaPods is not supported due to macro limitations).\n\n**Package.swift**\n\n```swift\n.target(name: \"MyApp\", dependencies: [\"ReactorKit\", \"ReactorKitSwiftUI\"])\n```\n\n## Examples\n\n- [Counter](https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Ftree\u002Fmain\u002FExamples\u002FCounter): The most simple and basic example of ReactorKit (UIKit)\n- [SwiftUI Counter](https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Ftree\u002Fmain\u002FExamples\u002FSwiftUICounter): A SwiftUI example using ObservedReactor and ReactorObserving\n- [GitHub Search](https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Ftree\u002Fmain\u002FExamples\u002FGitHubSearch): A simple application which provides a GitHub repository search\n- [RxTodo](https:\u002F\u002Fgithub.com\u002Fdevxoul\u002FRxTodo): iOS Todo Application using ReactorKit\n- [Cleverbot](https:\u002F\u002Fgithub.com\u002Fdevxoul\u002FCleverbot): iOS Messaging Application using Cleverbot and ReactorKit\n- [Drrrible](https:\u002F\u002Fgithub.com\u002Fdevxoul\u002FDrrrible): Dribbble for iOS using ReactorKit ([App Store](https:\u002F\u002Fitunes.apple.com\u002Fus\u002Fapp\u002Fdrrrible\u002Fid1229592223?mt=8))\n- [Passcode](https:\u002F\u002Fgithub.com\u002Fcruisediary\u002FPasscode): Passcode for iOS RxSwift, ReactorKit and IGListKit example\n- [Flickr Search](https:\u002F\u002Fgithub.com\u002FTaeJoongYoon\u002FFlickrSearch): A simple application which provides a Flickr Photo search with RxSwift and ReactorKit\n- [ReactorKitExample](https:\u002F\u002Fgithub.com\u002Fgre4ixin\u002FReactorKitExample)\n- [reactorkit-keyboard-example](https:\u002F\u002Fgithub.com\u002Ftechinpark\u002Freactorkit-keyboard-example): iOS Application example for develop keyboard-extensions using ReactorKit Architecture.\n- [TinyHub](https:\u002F\u002Fgithub.com\u002Ftospery\u002FTinyHubForIOS): Use ReactorKit develop the Github client\n\n## Dependencies\n\n- [RxSwift](https:\u002F\u002Fgithub.com\u002FReactiveX\u002FRxSwift) ~> 6.0\n\n## Requirements\n\n- Swift 6.0+\n- iOS 13.0+\n- macOS 10.15+\n- tvOS 13.0+\n- watchOS 6.0+\n\n## Installation\n\n**Podfile**\n\n```ruby\npod 'ReactorKit'\n```\n\n**Package.swift**\n\n```swift\nlet package = Package(\n  name: \"MyPackage\",\n  dependencies: [\n    .package(url: \"https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit.git\", .upToNextMajor(from: \"3.0.0\"))\n  ],\n  targets: [\n    .target(name: \"MyTarget\", dependencies: [\"ReactorKit\"])\n  ]\n)\n```\n\nReactorKit does not officially support Carthage.\n\n**Cartfile**\n\n```swift\ngithub \"ReactorKit\u002FReactorKit\"\n```\n\nMost Carthage installation issues can be resolved with the following:\n\n```sh\ncarthage update 2>\u002Fdev\u002Fnull\n(cd Carthage\u002FCheckouts\u002FReactorKit && swift package generate-xcodeproj)\ncarthage build\n```\n\n## Contribution\n\nAny discussions and pull requests are welcomed 💖\n\n- To development:\n\n  ```console\n  TEST=1 swift package generate-xcodeproj\n  ```\n\n- To test:\n\n  ```console\n  swift test\n  ```\n\n## Community\n\n### Join\n\n- **English**: Join [#reactorkit](https:\u002F\u002Frxswift.slack.com\u002Fmessages\u002FC561PETRN\u002F) on [RxSwift Slack](http:\u002F\u002Frxswift-slack.herokuapp.com\u002F)\n- **Korean**: Join [#reactorkit](https:\u002F\u002Fswiftkorea.slack.com\u002Fmessages\u002FC568YM2RF\u002F) on [Swift Korea Slack](http:\u002F\u002Fslack.swiftkorea.org\u002F)\n\n### Community Projects\n\n- [ReactorKit-Template](https:\u002F\u002Fgithub.com\u002Fgre4ixin\u002FReactorKit-Template)\n\n## Who's using ReactorKit\n\n\u003Cp align=\"center\">\n  \u003Cbr>\n  \u003Ca href=\"https:\u002F\u002Fwww.stylesha.re\">\u003Cimg align=\"center\" height=\"48\" alt=\"StyleShare\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F30255218-e16fedfe-966f-11e7-973d-7d8d1726d7f6.png\">\u003C\u002Fa>\n  \u003Ca href=\"http:\u002F\u002Fwww.kakaocorp.com\">\u003Cimg align=\"center\" height=\"36\" alt=\"Kakao\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F30324656-cbea148a-97fc-11e7-9101-ba38d50f08f4.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.wantedly.com\">\u003Cimg align=\"center\" height=\"48\" alt=\"Wantedly\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F5885032\u002F123386862-12314780-d5d2-11eb-91c6-f9dc14a329f0.png\">\u003C\u002Fa>\n  \u003Cbr>\u003Cbr>\n  \u003Ca href=\"http:\u002F\u002Fgetdoctalk.com\">\u003Cimg align=\"center\" height=\"48\" alt=\"DocTalk\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F30633896-503d142c-9e28-11e7-8e67-69c2822efe77.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.constantcontact.com\">\u003Cimg align=\"center\" height=\"44\" alt=\"Constant Contact\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F43634090-2cb30c7e-9746-11e8-8e18-e4fcf87a08cc.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.kt.com\">\u003Cimg align=\"center\" height=\"42\" alt=\"KT\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F43634093-2ec9e94c-9746-11e8-9213-75c352e0c147.png\">\u003C\u002Fa>\n  \u003Cbr>\u003Cbr>\n  \u003Ca href=\"https:\u002F\u002Fhyperconnect.com\u002F\">\u003Cimg align=\"center\" height=\"62\" alt=\"Hyperconnect\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F50819891-aa89d200-136e-11e9-8b19-780e64e54b2a.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Ftoss.im\u002Fcareer\u002F?category=engineering&positionId=7\">\u003Cimg align=\"center\" height=\"64\" alt=\"Toss\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F25301615\u002F236112886-9c24bd8d-65f3-419f-9669-1dbc575996a6.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fpay.line.me\">\u003Cimg align=\"center\" height=\"58\" alt=\"LINE Pay\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F68603\u002F68569839-7efdd980-04a2-11ea-8d7e-673831b1b658.png\">\u003C\u002Fa>\n  \u003Cbr>\u003Cbr>\n  \u003Ca href=\"https:\u002F\u002Fwww.gccompany.co.kr\u002F\">\u003Cimg align=\"center\" height=\"45\" alt=\"LINE Pay\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F931655\u002F84870371-32beeb80-b0ba-11ea-8530-0dc71c4e385e.png\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.kurly.com\u002F\">\u003Cimg align=\"center\" height=\"70\" alt=\"Kurly\" hspace=\"15\" src=\"https:\u002F\u002Fuser-images.githubusercontent.com\u002F5376577\u002F140284812-1f6d82c3-a1c9-488a-b059-d77825b5f962.png\">\u003C\u002Fa>\n  \u003Cbr>\u003Cbr>\n\u003C\u002Fp>\n\n> Are you using ReactorKit? Please [let me know](mailto:devxoul+reactorkit@gmail.com)!\n\n## Changelog\n\n- 2017-04-18\n  - Change the repository name to ReactorKit.\n- 2017-03-17\n  - Change the architecture name from RxMVVM to The Reactive Architecture.\n  - Every ViewModels are renamed to ViewReactors.\n\n## License\n\nReactorKit is under MIT license. See the [LICENSE](https:\u002F\u002Fgithub.com\u002FReactorKit\u002FReactorKit\u002Fblob\u002Fmain\u002FLICENSE) for more info.\n","ReactorKit 是一个用于构建响应式和单向数据流 Swift 应用程序的框架。它通过将 Flux 架构与响应式编程相结合，提供了一种清晰且可维护的方式来管理应用状态和用户交互。核心功能包括定义了视图（View）与反应器（Reactor）之间的职责分离，其中 Reactor 负责处理业务逻辑、状态管理和副作用，而 View 则专注于展示数据。此外，ReactorKit 支持 Storyboard，并提供了丰富的测试工具以确保代码质量。适用于需要高效管理复杂状态变化及用户交互的 iOS 或 macOS 应用开发场景。",2,"2026-06-11 03:10:24","top_language"]