過了幾天(尤其在這兩天)的摸索後, 終於把第一支App給兜出來了, 這只是個練習, 所以做的事情很單純 - 搜尋GitHub上的User

Source code 在: RAC-GitHubUserSearch

範例畫面: Imgur

swift還不太熟, 所以讀人家的code和寫code還不是那麼的流暢, 不過最大的問題還是在硬是想要用Swift + RAC 3.0, RAC 3.0現在雖然已經進入beta了, 但很多東西還不太穩定, 還在改, 自然不用說文件了, swift相關的官方文件幾乎是0, 這時候Google, source codes, 還有github上的每一則issue就是最好的朋友了

熟悉funtional language的人可能對reative的觀念會比較容易懂, 目前看過寫的比較不錯的文章是這篇: The introduction to Reactive Programming you’ve been missing

反正不就是把一切當stream串來串去的不就是了嗎?(其實會暈), bacon.js這幾張圖也有點幫助理解一些工具

在ReactiveCocoa方面, Colin Eberhardt寫了好幾篇文章還算蠻不錯的: http://blog.scottlogic.com/2015/04/24/first-look-reactive-cocoa-3.html http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1

不過針對RAC3的文章尚不完整加上RAC3 Swift的部份也還一直在改, 其實很多東西看code可能會比較清楚, 為了研究這個, 看了幾十篇文章, ObjectivC的相關的未必適用於Swift, 有些API在Swift上是改了名字改了用法, 甚至有些其實還沒有, 整理上蠻頭痛的

對於MVVM的部份, 我其實也還沒很認真搞懂, 還是有點似是而非

針對我的code裡面幾個地方來說明一下好了

DynamicProperty

DyamicProperty的觀念的部份可以看這篇: ReactiveCocoa (RAC3.0) by Swift

啥?日文的?沒關係, 看到關鍵字KVO就大概猜是跟KVO類似的東西, 我的code裡面ViewController.viewDidLoad的第一段:

viewModel.prop_keyword <~ keywordInput.rac_textSignal().toSignalProducer() |> catch {
    error in
    return SignalProducer<AnyObject?, NoError>(value:"")
}

這一段就是把UITextField打的字自動設定到viewModel.searchKeyword內, 這邊故意把searchKeyword宣告成private, 是為了要實驗這樣signal也還可以透過DynamicProperty設定這邊的值

這邊比較難懂的地方是"<~“, ”|>“, 這兩個都不是Swift標準的運算符, 而是ReactiveCocoa特有的, ”<~“尤其特別, 這跟我們慣用的左向右的運算符不太一樣, 這要由右向左看, 就是把左邊的signal producer的結果串到右邊的DynamicProperty, 而”|>“呢?對於reactive來說, 都是stream的操作, 所以Signal也是可以串接起來的, 透過”|>“, 那這邊為何要多一個”|> catch"呢? 因為DynamicProperty接的是SignalProducer<U, NoError>但textSignal的Error則非NoError, 需要靠catch來做error handling

DynamicProperty跟一般的KVO不同的地方就是它可以是雙向的, 因此可以用:

viewModel.prop_keyword.producer
        |> filter {
            return count($0 as! String) >= 3
        }

這邊就是把prop_keyword這個DynamicProperty當SignalProducer來用, 串到filter去

另外這一整段:

let filteredKeywordSignalProducer = viewModel.prop_keyword.producer
        |> filter {
            return count($0 as! String) >= 3
        }
        
        let throttledKeywordSignal = filteredKeywordSignalProducer |> throttle(1, onScheduler: QueueScheduler())
        
        throttledKeywordSignal |> map {
            value in
            return GitHubClient.searchUser(value as! String)
        }
        |> flatten(FlattenStrategy.Concat)
        |> start {
            value in
            self.viewModel.users = value
        }

這邊的目的是, 當UITextField打超過3個(含)字母以上時, 要透過GitHub API去搜尋使用者並改變viewModel users裡的值

其時map + flattern這邊本來想用flatMap來寫的, 不過flatMap似乎有點問題, 參考: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1966#issuecomment-99536117 以及這個commit: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1988

目前Signal和SignalProducer各有一個flatMap, compile會有ambiguous的情形, 看來這個commit會把SignalProducer那個拿掉, 但還是可以用map + flatten改寫

另外throtte的用意是為了避免太頻繁的送出API, 限制request的間隔在一秒內

接下來是signalForImage的部份:

func signalForImage(url: NSURL) -> SignalProducer {
        return SignalProducer {
            observer, disposable in
            let imgData = NSData(contentsOfURL: url)
            sendNext(observer, UIImage(data: imgData!)!)
            sendCompleted(observer)
            return
        }
    }

這部份是把載入網路上的圖這功能包裝成一個signal producer, 目的是為了下面這段:

signalForImage(url!)
            |> startOn(QueueScheduler())
            |> observeOn(UIScheduler())
            |> start
            {
                value in
                cell.imageView?.image = value
                return
            }

startOn(QueueScheduler())的目的就是要讓載入圖片這件事不要在主線程(main thead)發生, 而是把它丟到GCD queue, 最後UI thread接到通之後再把它顯示出來

好了, 先到這邊, 整個程式看起來很簡單, 但很多觀念還花了不少時間呀 orz