> 本文翻譯自:http://rx-marin.com/post/rxswift-rxcocoa-sample-split-laps-timer/
作者:[Marin Todorov ](http://www.underplot.com/)
----
我正在瀏覽[RxMarbles](http://rxmarbles.com/),完全被[`sample`](http://rxmarbles.com/#sample)函數(shù)所困擾。流程圖看起來很隨機:

起初我想 - “嘿,第二個序列正在被完全忽略!”,但是在我閱讀之后,我發(fā)現(xiàn):
`第一個序列的元素是由樣品發(fā)出的,而第二個序列的元素決定樣品發(fā)出的時間,所以在某種程度上是-實際上A,B,C,D確實被完全忽略。`
當我明白了`sample`做了些什么,我開始想知道這個功能是否有實際的用處:]
這讓我想創(chuàng)建一個拆分計時器(注: `split lap timer`)應用程序來測試`sample`可以為我們做什么。在完成的項目中,我有一個定時器發(fā)出時間值(也就是一個序列),同時我也想要抓住(注: grap)或者`sample`每一次用戶點擊(是另一個序列)按鈕的反應值。
下面是應用程序設置的流程圖:

這是完成后的應用程序的樣子:

讓我們開始構建這個應用吧:]
下面是我想要的拆分計時器應用的運行與功能:
1. 在程序啟動時就啟動計時器
2. 以 `MM:SS:MS` 的形式顯示運行時間
3. 當用戶點擊了按鈕`Split Lap`的時候添加一條拆分時間值
4. 以一個列表的形式展示拆分時間
5. 以列表頭的形式展示拆分時間數(shù)目總和
### 1. 啟動計時器
像我在[前一篇文章](http://rx-marin.com/post/rxswift-timer-sequence-manual-dispose-bag/)中關于手動處理包內(nèi)容那樣,我添加一個`timer`。
```
var timer: Observable!
```
同時在`viewDidLoad`中讓它每1/10秒運行一次。(我選擇只顯示一位數(shù)毫秒,所以不需要頻率更高:])
```
//create the timer
timer = Observable.interval(0.1, scheduler: MainScheduler.instance)
timer.subscribeNext({ msecs -> Void in
?print("\(msecs)00ms")
}).addDisposableTo(bag)
```
這會使計時器運行,并填滿控制臺:
```
000ms
100ms
200ms
300ms
400ms
500ms
600ms
700ms
800ms
900ms
1000ms
1100ms
```
酷,這很好:](你一定會想,“好吧,我已經(jīng)知道如何做這個了”)
### 2. 顯示當前已過去時間
這也是我已經(jīng)知道如何去做的部分。首先我寫了一個小的函數(shù),該函數(shù)會將已過去的時間格式化成想要的字符串。
```
func stringFromTimeInterval(ms: NSInteger) -> String {
?return String(format: "%0.2d:%0.2d.%0.1d",
? ?arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
}
```
然后回到`viewDidLoad`,我使用該函數(shù)綁定計時器到通過`Interface Builder`創(chuàng)建的Label中。
```
//wire the chrono
timer.map(stringFromTimeInterval)
?.bindTo(lblChrono.rx.text)
?.addDisposableTo(bag)
```
我真的很喜歡代碼運行的步驟,下面是代碼中一步步發(fā)生的事情:
```
timer -> 1,2,3 -> stringFromTimeInterval -> "string", "string" -> lblChrono
```
函數(shù)式的代碼非常棒,因為我已經(jīng)取得了兩個巨大勝利,我可以很容易重用`stringFromTimeInterval`,然后我也可以寫一些非常簡單的測試代碼。
與此同時,定時器`label`已經(jīng)成功顯示了當前運行的時間:

### 3. 當用戶點擊了`Split Lap`按鈕的時候,抓取當前拆分的時間
在這里我應該可以達到我的終極勝利:使用`sample`。前幾次嘗試并沒有讓我邁出大步,直到我意識到`rx.tap`也是一個`Observable`。
> Duh,一切都是`Observable`:
現(xiàn)在只剩下`timer`調用`sample`這一個問題了,并提供作為控制序列的按鈕的屬性`rx.tap`,就像這樣:`timer.sample(btnLap.rx.tap)`,怎么去做?(Whaaat?)
現(xiàn)在我每次點擊按鈕`sample`都會發(fā)出`timer`產(chǎn)生的最新值。由于我對數(shù)字不感興趣,但是在格式化字符串的時候我再次將結果使用`stringFromTimeInterval`轉換。
因為我需要創(chuàng)建一個分離的時間列表,我使用`scan`。實際上我第一次是想用`reduce`,因為我想在列表中累加值,但是后來意識到,我需要產(chǎn)生一個序列,同時這個序列發(fā)出每個新值得列表。因此我意識到了我得使用`scan`。
```
let lapsSequence = timer.sample(btnLap.rx_tap)
.map(stringFromTimeInterval)
.scan([String](), accumulator: {lapTimes, newTime in
return lapTimes + [newTime]
})
.shareReplay(1)
```
所以,每次`sample`發(fā)出新的拆分時間時,`scan`會掃描到目前為止所有拆分時間的數(shù)組。
不知道如何解釋`scan`更簡單,但是我會嘗試:在`RxSwift`中的任何時候,如果你正想使用`reduce`,那么你也可以使用`scan`代替:]
### 4. 顯示到目前為止所有的拆分時間表
OK, 所以我得到了`lapsSequence`發(fā)出的拆分時間數(shù)組。在參考了`RxExample`之后,我們就可以操作`UITableView`了。
```
//show laps in table
lapsSequence
.bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(row + 1)) \(element)"
}
.disposed(by: bag)
.addDisposableTo(bag)
```
同時我的APP已經(jīng)正常工作了。

我每一次點擊`Split Lap`按鈕,都會添加一條拆分時間到TableView上。Sweet~
### 5. 在TableView標題上顯示拆分時間總數(shù)
這部分是讓我最失望的。因為沒有一個綁定我可以用在TableView的Header上,同時我又不想添加數(shù)據(jù)源,這樣會使代碼復雜化。
于是我突然想到在我的控制器中添加一個Label,并將它作為TableView的header,然后將拆分次數(shù)總數(shù)綁定到這個label的`rx.text`上。
所以我添加一個label到視圖控制器上:
```
let tableHeaderView = UILabel()
```
然后是一個擴展,并將該label設置為TableView的header:
```
extension ViewController: UITableViewDelegate {
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableHeaderView
}
}
```
我知道如何設置TableView的代理到ViewController這個類中(讓我們回到ViewDidload中):
```
//set table delegate
tableView
.rx.setDelegate(self)
.addDisposableTo(bag)
```
現(xiàn)在又出現(xiàn)了變況,我不得不將`lapsSequence`從數(shù)組映射到單個字符上(例如:5次),并將該字符串綁定到TableView header上。
我過度興奮地想使用`scan`,但是代碼確實有些不優(yōu)雅。后來在RxSwift slak問了[KrunoslavZaher](), 他啟發(fā)了我,因為我有一個數(shù)組,這樣我就可以使用`map`將其轉換成字符串。
下面是最終在`viewDidLoad`中的代碼:
```
//update the table header
lapsSequence.map({ laps -> String in
return "\t\(laps.count) laps"
})
.startWith("\tno laps")
.bindTo(tableHeaderView.rx_text)
.addDisposableTo(bag)
```
因為`lapsSequence`發(fā)出的是所有拆分時間的數(shù)組,每次發(fā)出一個新的拆分時間,我只取該數(shù)組,并返回一個包含元素數(shù)量的字符串。
此外,我將初始值設置為`no laps`, 這樣很好。我將所有內(nèi)容直接綁定到`tableHeaderView.rx_text`上。
這是完整的應用程序!

您可以下載完成的項目,并在這里嘗試:[rx_laptimer.zip](https://github.com/qiuncheng/posted-articles-in-blog/tree/master/Demos).
希望這篇文章是有幫助的,如果你想聯(lián)系你,你可以找到我在這里[Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Frx-marin.com%2Fpost%2Frxswift-rxcocoa-sample-split-laps-timer%2F&ref_src=twsrc%5Etfw?ion=follow_link&screen_name=icanzilb&tw_p=followbutton)
> 注: 原文提供的代碼下載地址還不是Swift 3的,所以我在這里已經(jīng)更改了原作者提供的地址。代碼經(jīng)過`Xcode8.2.1`測試,完全可以測試通過。
對于英語好的同學,推薦閱讀作者的英文原文。 ?
?
?
?