KEMBAR78
Hello, ReactorKit  | PDF
Hello, ReactorKit! 👋
Suyeol Jeon https://github.com/devxoul
Jeon Suyeol
StyleShare Inc.
Open Source Lover
Then
URLNavigator
RxSwift
ObjectMapper
Why?
Why?
Massive View Controller
Why?
Massive View Controller
RxSwift State Managing
Massive View Controller
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
Massive View Controller
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
Massive View Controller
🙁
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
RxSwift State Managing
Cyclic Data Dependencies
RxSwift State Managing
currentValue increaseValue()
RxSwift State Managing
currentValue increaseValue()
last state
RxSwift State Managing
currentValue increaseValue()
last state
result
RxSwift State Managing
Cyclic Data Dependencies
RxSwift State Managing
Cyclic Data Dependencies
Pagination
List operation
Value update
...
RxSwift State Managing
After a while...
RxSwift State Managing
After a while...
Variable<Int>
PublishSubject<Int>
PublishRelay<[Item]>
Variable<User>
BehaviorSubject<String>
Variable<String>
I wanted to...
1. Avoid Massive View Controller
I wanted to...
1. Avoid Massive View Controller
2. Take advantages of RxSwift
I wanted to...
1. Avoid Massive View Controller
2. Take advantages of RxSwift
3. Manage states gracefully
ReactorKit
ReactorKit can...
1. Avoid Massive View Controller ✅
ReactorKit can...
1. Avoid Massive View Controller ✅
Separates responsibilities of view and logic
ReactorKit can...
1. Avoid Massive View Controller ✅
Separates responsibilities of view and logic
View Controller becomes simple
ReactorKit can...
2. Take advantages of RxSwift ✅
ReactorKit can...
2. Take advantages of RxSwift ✅
Based on RxSwift
ReactorKit can...
2. Take advantages of RxSwift ✅
Based on RxSwift
All of RxSwift features are available
ReactorKit can...
3. Manage states gracefully ✅
ReactorKit can...
3. Manage states gracefully ✅
Unidirectional data flow
ReactorKit can...
3. Manage states gracefully ✅
Unidirectional data flow
Modify states only in reduce()
ReactorKit can...
3. Manage states gracefully ✅
Unidirectional data flow
Modify states only in reduce()
State management became easy
ReactorKit can...
Be the future
~1.1K ⭐
~50K downloads
~900 apps
https://starcharts.herokuapp.com/ReactorKit/ReactorKit
ReactorKit can...
Be the future
~1.1K ⭐
~50K downloads
~900 apps
https://starcharts.herokuapp.com/ReactorKit/ReactorKit
Basic Concept
Basic Concept
Abstraction of User Interaction
Abstraction of View State
Basic Concept
Renders view states
Handles user interactions
ViewController,Cell,...
Basic Concept
protocol View {
associatedtype Reactor
var disposeBag: DisposeBag
// gets called when
// self.reactor is changed
func bind(reactor: Reactor)
}
Basic Concept
protocol StoryboardView {
associatedtype Reactor
var disposeBag: DisposeBag
// gets called when
// the view is loaded
func bind(reactor: Reactor)
}
// for Storyboard support
Basic Concept
Performs business logic
Manages states
Corresponds to view
Basic Concept
protocol Reactor {
associatedtype Action
associatedtype Mutation
associatedtype State
var initialState: State
}
Basic Concept
Based on RxSwift
Data Flow
Data Flow
Data Flow
Action → State ❌
Data Flow
Action → Mutation → State
Data Flow
Action → Mutation → State
State manipulator
Data Flow
State manipulator
Async-able
Action → Mutation → State
Data Flow
State manipulator
Async-able
Not exposed to view
Action → Mutation → State
Data Flow
(Action) -> Observable<Mutation>
(State, Mutation) -> State
Data Flow
Data Flow
class ProfileViewReactor: Reactor {
enum Action {
}
struct State {
}
}
Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
}
}
Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
var isFollowing: Bool // view state
}
}
Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
struct State {
var isFollowing: Bool // view state
}
}
Execute user follow API → Change state
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
Data Flow
class ProfileViewReactor: Reactor {
enum Action {
case follow // user interaction
}
enum Mutation {
}
struct State {
var isFollowing: Bool // view state
}
}
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
}
}
Data Flow
Data Flow
Tap follow button
Data Flow
Action.follow
Data Flow
Action.follow
Data Flow
UserService.follow()
Data Flow
UserService
Data Flow
Observable<Bool>
Data Flow
Data Flow
Mutation.setFollowing(true)
Data Flow
Mutation.setFollowing(true)
Data Flow
isFollowing = true
Data Flow
Update
follow button
Data Flow
More Examples
More Examples
Advanced
View Communications
Testing View and Reactor
View Communications
ProfileViewController
ProfileViewReactor
View Communications
UICollectionView
ProfileViewController
ProfileViewReactor
View Communications
UICollectionView
ProfileViewController
ProfileViewReactor
UserCell
View Communications
Passing user data
Observing button tap
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.User
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.User
2.User
View Communications - Passing user data
// ProfileViewReactor
struct State {
}
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
// ProfileViewController
let cell = collectionView.dequeue...
return cell
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
// ProfileViewController
let cell = collectionView.dequeue...
if let reactor = self.reactor {
}
return cell
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
View Communications - Observing button tap
ProfileView
Controller
UserCell
ProfileView
Reactor
View Communications - Observing button tap
ProfileView
Controller
UserCell
ProfileView
Reactor
1.rx.tap
View Communications - Observing button tap
ProfileView
Controller
UserCell
ProfileView
Reactor
2.Action.follow
1.rx.tap
View Communications - Observing button tap
ProfileView
Controller
UserCell
ProfileView
Reactor
2.Action.follow
1.rx.tap Reactive Extension
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
}
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var buttonTap: ControlEvent<Void> {
}
}
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var buttonTap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var buttonTap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
// ProfileViewController
cell.user = reactor.currentState.user
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var buttonTap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
// ProfileViewController
cell.user = reactor.currentState.user
cell.rx.buttonTap
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var tap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
// ProfileViewController
cell.user = reactor.currentState.user
cell.rx.buttonTap
.map { Reactor.Action.follow }
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var tap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
// ProfileViewController
cell.user = reactor.currentState.user
cell.rx.buttonTap
.map { Reactor.Action.follow }
.bind(to: reactor.action)
View Communications - Observing button tap
// UserCell
extension Reactive where Base: UserCell {
var tap: ControlEvent<Void> {
return self.base.followButton.rx.tap
}
}
// ProfileViewController
cell.user = reactor.currentState.user
cell.rx.buttonTap
.map { Reactor.Action.follow }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
View Communications
UICollectionView
ProfileViewController
ProfileViewReactor
UserCell
View Communications
UICollectionView
ProfileViewController
ProfileViewReactor
UserCell
UserCellReactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
UserCell
Reactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
UserCell
Reactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
2.CellReactor
UserCell
Reactor
View Communications - Passing user data
ProfileView
Controller
UserCell
ProfileView
Reactor
1.CellReactor
2.CellReactor
UserCell
Reactor
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
}
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
var userCellReactor: UserCellReactor?
}
View Communications - Passing user data
// ProfileViewReactor
struct State {
var user: User?
var userCellReactor: UserCellReactor?
}
// ProfileViewController
cell.user = reactor.currentState.user
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
Testing View and Reactor
What to test?
View
Reactor
Testing View and Reactor
What to test?
View
Action: on user interaction → action sent?
Reactor
Testing View and Reactor
What to test?
View
Action: on user interaction → action sent?
State: on state change → view updated?
Reactor
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?
Testing View and Reactor
How to test?
Testing View and Reactor
How to test?
Reactor.stub()
Testing View and Reactor
How to test?
Reactor.stub()
state: set fake state
Testing View and Reactor
How to test?
Reactor.stub()
state: set fake state
action: send fake action
Testing View and Reactor
How to test?
Reactor.stub()
state: set fake state
action: send fake action
actions: log received actions
Testing View and Reactor - View Action
Testing View and Reactor - View Action
When:
follow button taps
Then:
sends follow action
Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
// when
// then
Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
// when
// then
Testing View and Reactor - View Action
// given
let reactor = ProfileViewReactor()
reactor.stub.isEnabled = true
reactor.stub.state.value.user = User()
// when
// then
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
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
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
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)
Testing View and Reactor - View State
When:
following the user
Then:
button is selected
Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
// when
// then
Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
// when
// then
Testing View and Reactor - View State
// given
let cellReactor = UserCellReactor()
cellReactor.stub.isEnabled = true
let cell = UserCell()
cell.reactor = cellReactor
// when
// then
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
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)
Testing View and Reactor - Reactor State
When:
receive follow action
Then:
update following state
ProfileView
Reactor
Testing View and Reactor - Reactor State
// given
let reactor = ProfileViewReactor()
// when
// then
Testing View and Reactor - Reactor State
// given
let reactor = ProfileViewReactor()
// when
reactor.action.onNext(.follow)
// then
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)
Future Ideas
Development
Documentation
Community
Development
Testing Support
SectionReactor
AlertReactor
ModelReactor
Plugins
...
Nested stub
Dummy reactor
...
Building Extensions
Documentation
Best Practices
Translations
Code-level Documentation
Community
RxSwift Slack #reactorkit (English)
https://rxswift-slack.herokuapp.com
Swift Korea Slack #reactorkit (Korean)
http://slack.swiftkorea.org
https://reactorkit.io

Hello, ReactorKit 