RxSwift项目实践
RxSwift-MVVM
这个项目是入坑RxSwift以来的一些收获,历经多个真实项目的实践。我也一直在为写出简洁易懂的代码而努力学习和实践,当中难免有不足之处希望得到社区开源爱好者的指点,期待在与你的探讨中也能获得一些收获
项目介绍
-
iOS 界面业务逻辑
/* ViewController(action) ⏬ Reactor(transform(action:)) ⏬ Reactor(mutate(action:)) ⏬ Reactor(transform(mutation:)) ⏬ Reactor(reduce(state: State, mutation: Mutation)) ⏬ Reactor(transform(state:)) ⏬ ViewController(state) */ func bind(reactor: RepoListViewReactor) { // Action self.rx.viewDidLoad .map { Reactor.Action.refresh } .bind(to: reactor.action) .disposed(by: disposeBag) // State reactor.state.map { $0.repos } .filterEmpty() .distinctUntilChanged() .map { [Section(model: (), items: $0)] } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) }
-
Utility 通用工具集合
-
MVVMBase 项目基类
-
Networking 网络层封装
/* service ⏬ API(MVVMTargetType) ⏬ Moya(TargetType) */ service .repos(username: currentState.name, page: 1) .asObservable() .map(Mutation.setRepos)
-
Namespace 链式语法调用
tableView .mvvm.adhere(toSuperView: view) .mvvm.layout(snapKitMaker: { (make) in make.edges.equalToSuperview() })
项目依赖
- RxSwift/RxCocoa - Reactive Programming in Swift
- RxOptional - RxSwift extensions for Swift optionals and "Occupiable" types
- RxDataSources - UITableView and UICollectionView Data Sources for RxSwift (sections, animated updates, editing ...)
- Moya/RxSwift - Network abstraction layer written in Swift
- CocoaLumberjack/Swift - A fast & simple, yet powerful & flexible logging framework for Mac and iOS
- Kingfisher - A lightweight, pure-Swift library for downloading and caching images from the web
- SnapKit - A Swift Autolayout DSL for iOS & OS X
- MJRefresh - An easy way to use pull-to-refresh
- ReactorKit - A framework for a reactive and unidirectional Swift application architecture
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.
image
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 namedreactor
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 } .bind(to: reactor.action) .disposed(by: self.disposeBag) // state (Reactor -> View) reactor.state.map { $0.isFollowing } .bind(to: followButton.rx.isSelected) .disposed(by: 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
andState
. It also requires a property namedinitialState
.
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 andState
represents a view state.Mutation
is a bridge betweenAction
andState
. A reactor converts the action stream to the state stream in two steps:mutate()
andreduce()
.
image