モチベーション
ソースを解読していく中で、理解を深めるために、また忘れないように作業内容をまとめております。
完全な自分メモです。
TableViewの表示がすごくシンプル
TableViewの表示に必要な処理これだけなんですね。
items
.bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
.disposed(by: disposeBag)
引数は3つ?のようですね。
- cellIdentifier:Identifier used to dequeue cells セルを一意にする識別子
- cellType:どんなセルのタイプを使うか
- Genericsパラメータ: Trail Closureのような形をしていますね。(中のソースを見たらTrail Closureではありませんでした)
{ (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
どのような仕組みでTableViewを表示しているか深掘りしてみる
さて、tableView.rx.itemsがどんな形をしているのか覗いてみることにします。
public func items<S: Sequence, Cell: UITableViewCell, O : ObservableType>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
-> (_ source: O)
-> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
-> Disposable
where O.E == S {
return { source in
return { configureCell in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.items(dataSource: dataSource)(source)
}
}
}
ぱっと見で複雑ですね。
気を取り直して、渡されるパラメータを整理してみます。
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
余談ですが、DefaultCell(ほとんどカスタムCellを使うと思いますのでムダ知識)を使う場合、
CellはUITableViewCellを継承していて、Cell.selfをデフォルト引数として渡しているので
cellType引数は省略できます。
items
.bindTo(tableView.rx.items(cellIdentifier: "Cell")) { (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
こんな感じにできます。
まずは、関数を読み解くと
func items(引数) -> (戻り値){
}
の形がシンプルな関数の形になるのでそれを念頭に置くと
func items(cellIdentifier: String, cellType: Cell.Type = Cell.self) -> (戻り値)
// 戻り値がこの形のClosureになっていると読み取れます。
(_ source: O)
-> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
-> Disposable
どうやらTrail Closuredではなく、Closure式が戻り値として返却されているようです。
気になるのは、source(ObservableType)がどこから渡ってくるのか、
bindToすることにより、Observable Sequenceが渡されてくるようです。
reduxでのmiddle wareからdispatchが渡されるみたいなものだとイメージしています。
(こちらは、深掘りできていません。)
さて、コアな部分の
return { configureCell in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.items(dataSource: dataSource)(source)
}
closure式で、さらに引数にconfigureCellを受け取りDisposableを返却しています。
swiftのClosureは戻り値を省略することができるので、in句の前に戻り値が記述ありませんが、
Disposableを返却します。
宣言も見ると以下のようになっています。
(_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void) -> Disposable
configureCellクロージャーは、以下のようなClosure式がController側で記述していますし、
名前からもCellの見た目(ここではテキスト)を設定している部分ですね。
{ (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell) // ここがClosureとして渡される部分です。
return cell
当たり前だと思いますが、cellの形を自由に外から決められるようにしたいから、
closure式を渡す形になっているわけですね。納得です。
さてCellの形を決めているのは理解できましたが、DataSourceを作成する部分をどのように作っているか深掘りしてみたいと思います。
さらに深掘り
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
(処理)
return cell
}
つまり、RxTableViewReactiveArrayDataSourceSequenceWrapperのinstanceを作成して、dataSourceという形にしています。中のソースも見ましたが、このDataSourceはRx TableViewのDataSourceであり、通常のTableViewのDataSourceとは異なります。
少しRxTableViewReactiveArrayDataSourceSequenceWrapperをソースを見てみると、
override init(cellFactory: @escaping CellFactory) {
super.init(cellFactory: cellFactory)
}
typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell
let cellFactory: CellFactory
init(cellFactory: @escaping CellFactory) {
self.cellFactory = cellFactory
}
なるほど、initialize処理が走ると、CellFactoryの上部で記述した、closure式を内部で保持するようにしているわけですね。このClosureは先にCellの形を決めている部分です。
さて、Cell表示に欠かすことのできないcellForRowAtメソッドもどきは、
以下のように保持されたClosure式をCallしておりました。
override func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cellFactory(tableView, indexPath.item, itemModels![indexPath.row])
}
このcellForRowAtはどこから実行されるんだろうと調べて見ましたら、
RxTableViewDataSourceProxyというクラスがCallしていました。
ちょっとだけ触れると、
tableView.dataSouce = self
単純にControllerからTableView DataSourceを操作する場合は、このように記述すると思いますが、
今回は、RxTableViewDataSourceProxyがTableView DataSourceを操作しておりました。
public class RxTableViewDataSourceProxy
: DelegateProxy
, UITableViewDataSource // ←このProxyがDataSourceをdelegateしているようです。
, DelegateProxyType {
(省略)
// UITableViewDataSourceのcellForRowAtを実行
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ここで上部で宣言した、RxTableViewReactiveArrayDataSourceのcellForRowAtメソッドが実行されています。
return (_requiredMethodsDataSource ?? tableViewDataSourceNotSet).tableView(tableView, cellForRowAt: indexPath)
}
(省略)
// https://github.com/ReactiveX/RxSwift/issues/907
private func refreshTableViewDataSource() {
if self.tableView?.dataSource === self {
if _requiredMethodsDataSource != nil && _requiredMethodsDataSource !== tableViewDataSourceNotSet {
self.tableView?.dataSource = self
}
else {
self.tableView?.dataSource = nil
}
}
}
最後にDataSourceを眺めてみることにします。雰囲気理解できました。
observedEventについては深掘りできておりません。
class RxTableViewReactiveArrayDataSourceSequenceWrapper<S: Sequence>
: RxTableViewReactiveArrayDataSource<S.Iterator.Element>
, RxTableViewDataSourceType {
typealias Element = S
override init(cellFactory: @escaping CellFactory) {
super.init(cellFactory: cellFactory)
}
func tableView(_ tableView: UITableView, observedEvent: Event<S>) {
UIBindingObserver(UIElement: self) { tableViewDataSource, sectionModels in
let sections = Array(sectionModels)
tableViewDataSource.tableView(tableView, observedElements: sections)
}.on(observedEvent)
}
}
// Please take a look at `DelegateProxyType.swift`
class RxTableViewReactiveArrayDataSource<Element>
: _RxTableViewReactiveArrayDataSource
, SectionedViewDataSourceType {
typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell
var itemModels: [Element]? = nil
func modelAtIndex(_ index: Int) -> Element? {
return itemModels?[index]
}
func model(at indexPath: IndexPath) throws -> Any {
precondition(indexPath.section == 0)
guard let item = itemModels?[indexPath.item] else {
throw RxCocoaError.itemsNotYetBound(object: self)
}
return item
}
let cellFactory: CellFactory
init(cellFactory: @escaping CellFactory) {
self.cellFactory = cellFactory
}
override func _tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemModels?.count ?? 0
}
override func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cellFactory(tableView, indexPath.item, itemModels![indexPath.row])
}
// reactive
func tableView(_ tableView: UITableView, observedElements: [Element]) {
self.itemModels = observedElements
tableView.reloadData()
}
}
さて、Disposedの戻り値を返却している、関数は以下のものですね。
return { configureCell in
let dataSource = (dataSource作成処理)
return self.items(dataSource: dataSource)(source)
}
少し中を覗いてみたいと思います。
public func items<
DataSource: RxTableViewDataSourceType & UITableViewDataSource,
O: ObservableType>
(dataSource: DataSource)
-> (_ source: O)
-> Disposable
where DataSource.Element == O.E {
return { source in
// This is called for sideeffects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UITableView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base,
dataSource: dataSource,
retainDataSource: true)
{ [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = tableView else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
}
}
まずは、関数を読み解くと
func items(引数) -> (戻り値){
}
の形がシンプルな関数の形になるのでそれを念頭に置くと
(dataSource: DataSource)
(_ source: O) -> Disposable
戻り値がClosureとなっているわけですね。
sourceは上でもでてきた、Observable Sequenceですね。
// sourceはObservableSequenceを引数に受け取っている
return { source in
_ = self.delegate
// self.base = tableView
return source.subscribeProxyDataSource(ofObject: self.base,
dataSource: dataSource,
retainDataSource: true)
{ [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = tableView else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
}
このsubscribeProxyDataSource以降は難解で詳しく追えていないのですが、
delegateを結びついているところだけ
extension ObservableType {
func subscribeProxyDataSource<P: DelegateProxyType>(ofObject object: UIView, dataSource: AnyObject, retainDataSource: Bool, binding: @escaping (P, Event<E>) -> Void)
-> Disposable {
let proxy = P.proxyForObject(object) // ここでtableView.dataSource.delegateと結びつけています
(省略)
P.proxyForObject(object)からrefreshTableViewDataSource()が呼び出されています。
private func refreshTableViewDataSource() {
if self.tableView?.dataSource === self {
if _requiredMethodsDataSource != nil && _requiredMethodsDataSource !== tableViewDataSourceNotSet {
self.tableView?.dataSource = self
}
else {
self.tableView?.dataSource = nil
}
}
}
整理すると、
iOSのライフサイクルの中で、tableView DataSourceのcellForRowAtが実行されると、RxTableViewDataSourceProxyがTableViewのDataSourceとBindされているので、
override func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cellFactory(tableView, indexPath.item, itemModels![indexPath.row])
}
が実行される。
また、cellFactoryはgenericsで、下記のものが実行される。
{ (tv, i, item) in
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
GenericsでProtocol制約を複数したいときは&でつなげる
DataSource: RxTableViewDataSourceType & UITableViewDataSource
こういうことが出来るわけですね。