一目了然

最近,我观看了一段有关iOS架构的视频,讲师在演讲中提到了一种名为ReSwift的架构。 几天后,这个名字再次出现在SwiftCoder播客的一集中。 因此,我决定进行一些研究并尝试找出答案。

介绍

ReSwift的GitHub页面引用了以下内容。 ReSwift是Swift中单向数据流架构的类似Redux的实现。 ReSwift可帮助您将应用程序组件的三个重要方面分开:

  • 状态:在ReSwift应用中,整个应用状态都明确存储在数据结构中。
  • 视图:在ReSwift应用程序中,当状态更改时,视图也会更新。 您的视图成为当前应用程序状态的简单可视化。
  • 状态更改:在ReSwift应用中,您只能通过操作执行状态更改。 动作是描述状态变化的小数据。

ReSwift还依赖于一些原则:

  • 商店以单个数据结构的形式存储整个应用程序状态。 只能通过将操作调度到商店来修改此状态。 每当商店中的状态发生变化时,商店都会通知所有观察者。
  • 动作是描述状态变化的一种声明方式。 动作不包含任何代码,它们由商店使用并转发给减速器。
  • 减速器将通过为每个动作实施不同的状态更改来处理这些动作。 Reducer提供了纯函数,这些函数基于当前操作和当前应用程序状态创建新的应用程序状态。

示范

我将演示如何在一个简单的项目中采用ReSwift。 该项目使用SWAPI来获取《星球大战》的电影,并在表格视图中显示它们。 以下是我的视图控制器的直观展示。

让我们开始定义我的State 。 因为我的项目非常简单,所以我只用一个enum来描述FilmsState

 struct AppState: StateType { 
let filmsState: FilmsState
}
 enum FilmsState { 
case loading
case finished([Film])
}

其次,我创建三个不同的Action ,包括LoadingFilmsActionSetFilmsActionfetchFilms 。 由于影片的数据是从SWAPI获取的,并且此操作是异步的,因此fetchFilms实际上是ActionCreator而不是Action类型。 提取影片的数据完成后,我使用Store来调度SetFilmsAction以便更新表格视图。 此外,我编写了一个WebService类来处理网络操作,您可以在本文中找到更多详细信息。

 struct LoadingFilmsAction: Action {} 
 struct SetFilmsAction: Action { 
let films: [Film]
}
 func fetchFilms(with webService: WebServiceProtocol = WebService()) -> (AppState, Store) -> Action? { 
return { state, store in
webService.load(resource: Film.all, completion: { (result) in
switch result {
case let .success(films):
store.dispatch(SetFilmsAction(films: films))
case let .failure(error):
print(error)
}
})
  return LoadingFilmsAction() 
}
}

我的项目的Reducer也非常简单,它只是基于先前的State和传入的Action返回一个新的State

 func appReducer(action: Action, state: AppState?) -> AppState { 
return AppState(filmsState: filmsReducer(action: action, state: state?.filmsState))
}
 func filmsReducer(action: Action, state: FilmsState?) -> FilmsState { 
switch action {
case let action as SetFilmsAction:
return .finished(action.films)
case _ as LoadingFilmsAction:
return .loading
default:
return state ?? .finished([])
}
}

然后,我在AppDelegate的文件内创建项目的mainStore作为全局变量。

 let mainStore = Store(reducer: appReducer, state: nil) 

最后,我将所有内容连接到FilmListViewController 。 让FilmListViewController符合StoreSubscriber协议并实现newState方法以更新UI。

 extension FilmListViewController: StoreSubscriber { 
func newState(state: FilmsState) {
switch state {
case .loading:
loadingView.isHidden = false
loadingView.startAnimating()
tableView.reloadData()
case let .finished(films):
self.films = films
loadingView.isHidden = true
tableView.isHidden = false
tableView.reloadData()
}
}
}

订阅StateviewDidLoad方法中更改,并在deinit方法中取消订阅。

 override func viewDidLoad() { 
// ...
  mainStore.subscribe(self) { (subscription) in 
subscription.select { (state) in
state.filmsState
}
}
}
 deinit { 
mainStore.unsubscribe(self)
}

viewDidLoad方法中调度fetchFilms操作,它将触发加载视图并通过SWAPI来获取影片的数据。

 override func viewDidLoad() { 
// ...
  mainStore.dispatch(fetchFilms()) 
}

示例项目在这里。

结论

单向流意味着应用程序中的所有数据都遵循相同的生命周期模式,从而使应用程序的逻辑更具可预测性。 通过极大地限制状态改变的方式,您的应用程序变得更易于理解,并且可以与许多协作者一起工作。 它还鼓励数据规范化,这样您就不会以同一数据的多个独立副本结尾而不会相互意识到。 此外, StoreStateActionReducer都是简单的结构或功能,并且更易于维护和测试。 我个人认为,这是建立复杂应用程序并与同一项目中的许多团队成员合作的好方法。 欢迎任何评论和反馈,请分享您的想法。 谢谢。