This document introduces ReactorKit, an open source framework that helps manage state and handle user interactions using RxSwift. It discusses some of the motivations for ReactorKit like avoiding massive view controllers and managing state changes gracefully. The basic concepts of ReactorKit are explained including the unidirectional data flow from actions to mutations to state updates. Examples of user interactions and state changes are provided. Advanced topics covered include view communications between different reactors and testing views and reactors. Future ideas for expanding ReactorKit are proposed like improving testing support and documentation.
16. RxSwift State Managing
After a while...
Variable<Int>
PublishSubject<Int>
PublishRelay<[Item]>
Variable<User>
BehaviorSubject<String>
Variable<String>
36. Basic Concept
protocol View {
associatedtype Reactor
var disposeBag: DisposeBag
// gets called when
// self.reactor is changed
func bind(reactor: Reactor)
}
37. Basic Concept
protocol StoryboardView {
associatedtype Reactor
var disposeBag: DisposeBag
// gets called when
// the view is loaded
func bind(reactor: Reactor)
}
// for Storyboard support
52. Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
var isFollowing: Bool // view state
}
}
53. Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
var isFollowing: Bool // view state
}
}
Execute user follow API → Change state
54. Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
var isFollowing: Bool // view state
}
}
Execute user follow API → Change state
Async
55. Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
enum Mutation {
}
struct State {
var isFollowing: Bool // view state
}
}
56. Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
enum Mutation {
case setFollowing(Bool) // change state
}
struct State {
var isFollowing: Bool // view state
}
}
81. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
82. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
// ProfileViewController
let cell = collectionView.dequeue...
return cell
83. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
// ProfileViewController
let cell = collectionView.dequeue...
if let reactor = self.reactor {
}
return cell
84. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
// ProfileViewController
let cell = collectionView.dequeue...
if let reactor = self.reactor {
cell.user = reactor.currentState.user
}
return cell
99. View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
100. View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
UserCell
Reactor
101. View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
UserCell
Reactor
102. View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
2.CellReactor
UserCell
Reactor
103. View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
2.CellReactor
UserCell
Reactor
104. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
105. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
var userCellReactor: UserCellReactor?
}
106. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
var userCellReactor: UserCellReactor?
}
// ProfileViewController
cell.user = reactor.currentState.user
107. View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
var userCellReactor: UserCellReactor?
}
// ProfileViewController
cell.user = reactor.currentState.user
cell.reactor = reactor.currentState.userCellReactor
109. Testing View and Reactor
What to test?
View
Action: on user interaction → action sent?
Reactor
110. Testing View and Reactor
What to test?
View
Action: on user interaction → action sent?
State: on state change → view updated?
Reactor
111. Testing View and Reactor
What to test?
View
Action: on user interaction → action sent?
State: on state change → view updated?
Reactor
State: on action receive → state updated?
119. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
// when
// then
120. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
// when
// then
121. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
// when
// then
122. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
let viewController = ProfileViewController()
viewController.reactor = reactor
// when
// then
123. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
let viewController = ProfileViewController()
viewController.reactor = reactor
// when
let button = viewController.userCell.followButton
// then
124. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
let viewController = ProfileViewController()
viewController.reactor = reactor
// when
let button = viewController.userCell.followButton
button.sendActions(for: .touchUpInside)
// then
125. Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
let viewController = ProfileViewController()
viewController.reactor = reactor
// when
let button = viewController.userCell.followButton
button.sendActions(for: .touchUpInside)
// then
let lastAction = reactor.stub.actions.last
XCTAssertEqual(lastAction, .follow)
126. Testing View and Reactor - View State
When:
following the user
Then:
button is selected
127. Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
// when
// then
128. Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
// when
// then
129. Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
let cell = UserCell()
cell.reactor = cellReactor
// when
// then
130. Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
let cell = UserCell()
cell.reactor = cellReactor
// when
cellReactor.stub.state.value.isFollowing = true
// then
131. Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
let cell = UserCell()
cell.reactor = cellReactor
// when
cellReactor.stub.state.value.isFollowing = true
// then
XCTAssertTrue(cell.followButton.isSelected)
132. Testing View and Reactor - Reactor State
When:
receive follow action
Then:
update following state
ProfileView
Reactor
133. Testing View and Reactor - Reactor State
// given
let reactor = ProfileViewReactor()
// when
// then
134. Testing View and Reactor - Reactor State
// given
let reactor = ProfileViewReactor()
// when
reactor.action.onNext(.follow)
// then
135. Testing View and Reactor - Reactor State
// given
let reactor = ProfileViewReactor()
// when
reactor.action.onNext(.follow)
// then
let user = reactor.currentState.user
XCTAssertEqual(user?.isFollowing, true)
139. Community
RxSwift Slack #reactorkit (English)
https://rxswift-slack.herokuapp.com
Swift Korea Slack #reactorkit (Korean)
http://slack.swiftkorea.org