ReactiveCocoa 信號操作

這篇文章講解在ReactiveCocoa中關于Signal的一些常用的操作凯亮,涉及到信號的轉換操作等罗捎。
一.Signal可以通過oberver函數來監(jiān)聽數據流中值的變化

let (signal, observer) = Signal<String, NoError>.pipe()
signal.observe { event in
    switch event {
    case let .value(value):  //當Observer 發(fā)送一個value事件的時候 執(zhí)行
        print("Value: \(value)")
    case let .failed(error):  //當Observer 發(fā)送一個Error事件的時候 執(zhí)行
        print("Failed: \(error)")
    case .completed: // 當Observer 發(fā)送Completed事件的時候 執(zhí)行
        print("Completed")
    case .interrupted: // 當Observer 發(fā)送Interrupted事件的時候 執(zhí)行 
        print("Interrupted")
    }
}

observer.send(value: "1") //print Value: 1 
observer.sendCompleted() //print  Completed
observer.send(value: "2") // no print
這里我們需要注意的是 Completed 和 Interrupted 代表當前事件流的結束。

二.事件流的轉換操作枪蘑,這些操作會將當前事件流轉換為一個新的事件流熙宇。
2.1 signal.map操作被用于將一個具有values的事件流 轉換為一個新的具有results的事件流围段。

let (signal, observer) = Signal<String, NoError>.pipe()
        
signal
.map { string in
     return string.uppercased() //在這里一定要return 一個value
}
.observeValues { value in print(value) }
        
observer.send(value: "a")     // Prints A
observer.send(value: "b")     // Prints B
observer.send(value: "c")     // Prints C
//需要注意的是 在map函數中亏栈,一定要顯示的return一個value,否則signal將無法繼續(xù)傳遞宏赘,
//因為它不知道當前value經過map操作轉換成了什么绒北。

2.2 signal. filter操作用于過濾某些signal。在某些場景中察署,可能會產生一些我們不想處理的signal闷游,這時我們可以用filter來過濾掉我們不想處理的signal。就好比過濾掉數組中某些我們不想要的數據贴汪。接下來脐往,我們舉個例子,用來過濾掉value為奇數的signal的例子

let (signal, observer) = Signal<Int, NoError>.pipe()
        
signal
 .filter { number in number % 2 == 0 }
 .observeValues { value in print(value) }
        
 observer.send(value: 1)     // Not printed
 observer.send(value: 2)     // Prints 2
 observer.send(value: 3)     // Not printed
 observer.send(value: 4)     // prints 4

需要注意的是 filter函數必須return一個bool類型的數據扳埂,當值為true的時候, signal將會被保留业簿,
值為false的時候,signal將會被過濾掉阳懂。

2.3 signal.reduce函數梅尤,用來將事件流中所有的數據結合為一個新的value.注意的是這個最終的value僅僅會在輸入流complete的時候被發(fā)送, 當然如果事件流interrupted,我們同樣不會得到任何結果岩调。例如: 我們對一個int數據流中的所有value進行求和巷燥,獲得最終的結果

let (signal, observer) = Signal<Int, NoError>.pipe()
//reduce需要傳遞一個初始的結果,和javascript中數組的reduce函數相似
//例如求和可以給初始值為0号枕, 求積可以初始值為1
signal
    .reduce(0) { $0 + $1 }
    .observeValues { value in print(value) }

observer.send(value: 1)     // nothing printed
observer.send(value: 2)     // nothing printed
observer.send(value: 3)     // nothing printed
observer.sendCompleted()   // prints 6

2.4 signal.collect() 操作用于聚合事件流中values,轉換成為一個array value.同樣這個最終的 array value僅僅會在輸入流complete的時候被發(fā)送缰揪。

let (signal, observer) = Signal<Int, NoError>.pipe()

signal
    .collect()
    .observeValues { value in print(value) }

observer.send(value: 1)     // nothing printed
observer.send(value: 2)     // nothing printed
observer.send(value: 3)     // nothing printed
observer.sendCompleted()   // prints [1, 2, 3]

三:事件流的組合方式(combineLatest, zip, merge, concat, latest)
3.1 Signal.combineLatest函數,可以用來結合兩個或多個事件流的最新值葱淳。結合的事件流中钝腺,必須每個事件流都發(fā)送了一個值,這時Signal.combineLatest函數產生的新的事件流才會有結果輸出赞厕。在此之后拍屑,任何一個結合的事件流有新值發(fā)送時,都會導致結合后的事件流有新值輸出坑傅。當然僵驰,在這過程中 如果有siganl被Interrupted,那么結合后的事件流將會立即產生Interrupted響應。

let (numbersSignal, numbersObserver) = Signal<Int, NoError>.pipe()
let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
        
let signal = Signal.combineLatest(numbersSignal, lettersSignal)
signal.observeValues { next in print("Next: \(next)") }
signal.observeCompleted { print("Completed") }
        
numbersObserver.send(value: 0)      // nothing printed
numbersObserver.send(value: 1)      // nothing printed
lettersObserver.send(value: "A")    // prints (1, A)
numbersObserver.send(value: 2)      // prints (2, A)
numbersObserver.sendCompleted()  // nothing printed
lettersObserver.send(value: "B")    // prints (2, B)
lettersObserver.send(value: "C")    // prints (2, C)
lettersObserver.sendCompleted()  // prints "Completed"
let (numbersSignal, numbersObserver) = Signal<Int, NoError>.pipe()
let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
        
let signal = Signal.combineLatest(numbersSignal, lettersSignal)
signal.observeValues { next in print("Next: \(next)") }
signal.observeCompleted { print("Completed") }
signal.observeInterrupted {
       print("Interrupted")
}
        
numbersObserver.send(value: 0)      // nothing printed
numbersObserver.send(value: 1)      // nothing printed
lettersObserver.send(value: "A")    // prints (1, A)
numbersObserver.send(value: 2)      // prints (2, A)
numbersObserver.sendInterrupted()  // prints Interrupted
lettersObserver.send(value: "B")    // nothing printed
lettersObserver.send(value: "C")    // nothing printed

3.2 Signal.zip函數將兩個或多個事件流的值成對的組合起來,產生一個數據類型為tuple的輸出流.那么這個成對我們怎么理解呢蒜茴?也就是說任何第n個元祖的元素對應的是輸入流的第n個元素星爪,這意味著直到每個輸入流都發(fā)送了第n個值,輸出流才會發(fā)送第n個值粉私。
zip函數在實際開發(fā)中應用比較多顽腾,比如某個頁面加載完畢以后,需要同時請求兩個數據接口诺核,只有當兩個數據接口均響應回數據以后抄肖,再去刷新頁面。這個時候 我們就可以把每個請求數據接口當做一個輸入流事件窖杀,然后對這兩個輸入流事件進行zip,當數據響應回來以后發(fā)送value事件漓摩,這時通過zip后的事件的observer函數 進行監(jiān)聽,刷新頁面入客。

let (numbersSignal, numbersObserver) = Signal<Int, NoError>.pipe()
let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
        
let signal = Signal.zip(numbersSignal, lettersSignal)
signal.observeValues { next in print("Next: \(next)") }
signal.observeCompleted { print("Completed") }
        
numbersObserver.send(value: 0)      // nothing printed
numbersObserver.send(value: 1)      // nothing printed
lettersObserver.send(value: "A")    // prints (0, A)
numbersObserver.send(value: 2)      // nothing printed
numbersObserver.sendCompleted()  // nothing printed
lettersObserver.send(value: "B")    // prints (1, B)
lettersObserver.send(value: "C")    // prints (2, C) & "Completed"
// zip后的輸出流的Completed事件什么時候執(zhí)行呢管毙?在我看來,
//當某個輸入流所輸入的值都已經被zip后的輸出流所發(fā)送桌硫,
//然后該輸入流發(fā)送Completed事件夭咬,那么輸出流就會觸發(fā)Completed事件。
//如果該輸入流還有value未被輸出流所發(fā)送铆隘,
//那么即使輸入流發(fā)送Completed事件卓舵,輸出流也無法觸發(fā)Completed事件。
//如果大家有不同意見膀钠,歡迎評論區(qū)提出你的論證边器。

3.3 signal.flatten操作 用于將多個流碾壓為一個單一的流。最終單一的流變成外部流的結果托修。

let values = [
[ 1,    2,      3 ], // siganl 1
   [ 4,      5,     6 ], // siganl 2
         [ 7,     8 ], // // siganl 3
]

//以下為3種不同的碾壓策略對應的結果
let merge =
[ 1, 4, 2, 7,5, 3,8,6 ]

let concat = 
[ 1,    2,      3,4,      5,     6,7,     8]

let latest =
[ 1, 4,    7,     8 ]

3.3.1 signal.flatten(.merge),多個事件流的merge操作會將內部事件流的每個值轉發(fā)給外部事件流忘巧。

let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
let (numbersSignal, numbersObserver) = Signal<String, NoError>.pipe()
let (signal, observer) = Signal<Signal<String, NoError>, NoError>.pipe()
       
//將signal的內部事件流進行合并,
signal.flatten(.merge).observeValues { print($0) } 

observer.send(value: lettersSignal)
observer.send(value: numbersSignal) //將lettersSignal睦刃,numbersSignal 添加到signal的內部事件流
//signal則為外部事件流
observer.sendCompleted()
        
lettersObserver.send(value: "a")    // prints "a"
numbersObserver.send(value: "1")    // prints "1"
lettersObserver.send(value: "b")    // prints "b"
numbersObserver.send(value: "2")    // prints "2"
lettersObserver.send(value: "c")    // prints "c"
numbersObserver.send(value: "3")    // prints "3"

3.3.2signal.flatten(.concat) 操作砚嘴,用來序列化內部事件流的事件。怎么理解這個策略呢涩拙,其實你可以考慮一下javascript中數組的concat操作际长,它是將兩個數組中的數據拼接成一個數組,例如: [1, 2, 3].concat([4,5,6]) = [1,2,3,4,5,6]兴泥。所以工育,signal中的concat同樣的道理,將多個內部信號的值拼接成一個單一的數據流搓彻,它的拼接策略是當第一個內部信號發(fā)送complete以后如绸,外部信號才會接受第二個內部信號發(fā)送的值嘱朽,所以 內部信號的順序很重要。

let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
let (numbersSignal, numbersObserver) = Signal<String, NoError>.pipe()
let (signal, observer) = Signal<Signal<String, NoError>, NoError>.pipe()

signal.flatten(.concat).observeValues { print($0) }
observer.send(value: lettersSignal)
observer.send(value: numbersSignal)
observer.sendCompleted()

//因為內部信號的順序為lettersSignal,numbersSignal 所以外部信號先接收lettersSignal發(fā)送的值怔接,
//當lettersSignal發(fā)送Completed事件以后搪泳,外部信號會依次接收numbersSignal發(fā)送的數據
//所以它和數組的concat函數是不是很相似呢?如果大家有些許懷疑扼脐,
//可以顛倒一下內部信號的順序看一下打印事件
numbersObserver.send(value: "1")    // nothing printed
lettersObserver.send(value: "a")    // prints "a"
lettersObserver.send(value: "b")    // prints "b"
numbersObserver.send(value: "2")    // nothing printed
lettersObserver.send(value: "c")    // prints "c"
lettersObserver.sendCompleted()     // nothing printed
numbersObserver.send(value: "3")    // prints "3"
numbersObserver.sendCompleted()     // nothing printed

3.3.3signal.flatten(. latest) 操作岸军,從最新的事件流轉發(fā)值。那么怎么理解這個策略呢瓦侮,當我們向外部事件流中添加多個內部事件流時艰赞,多個內部事件流只有最后一個事件流轉發(fā)值才會被外部事件流接收。

let (lettersSignal, lettersObserver) = Signal<String, NoError>.pipe()
let (numbersSignal, numbersObserver) = Signal<String, NoError>.pipe()
let (signal, observer) = Signal<Signal<String, NoError>, NoError>.pipe()

signal.flatten(.latest).observeValues { print($0) }

observer.send(value: lettersSignal) // nothing printed 
//此時signal中只有一個lettersSignal內部事件流, 
//因為numbersSignal此時還不是signal的內部事件流肚吏,所以signal不會收到numbersObserver發(fā)送的數據
numbersObserver.send(value: "1")    // nothing printed
//因為lettersSignal此時為signal中唯一的內部事件流方妖,所以signal會接收到lettersObserver發(fā)送的數據
lettersObserver.send(value: "a")    // prints "a"
lettersObserver.send(value: "b")    // prints "b"
numbersObserver.send(value: "2")    // nothing printed
//此時numbersSignal會將signal中l(wèi)ettersSignal內部事件流給替換掉, 
//signal只會接收numbersObserver發(fā)送的數據须喂,
//而不會在接收lettersObserver發(fā)送的數據. 這就是最新(latest)策略
observer.send(value: numbersSignal) // nothing printed
lettersObserver.send(value: "c")    // nothing printed
numbersObserver.send(value: "3")    // prints "3"

文章內容參考鏈接: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/BasicOperators.md。以上內容如有錯誤或不嚴謹的地方趁蕊,歡迎大家在評論區(qū)指出問題所在坞生,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末掷伙,一起剝皮案震驚了整個濱河市是己,隨后出現的幾起案子,更是在濱河造成了極大的恐慌任柜,老刑警劉巖卒废,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異宙地,居然都是意外死亡摔认,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門宅粥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來参袱,“玉大人,你說我怎么就攤上這事秽梅∧ㄊ矗” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵企垦,是天一觀的道長环壤。 經常有香客問我,道長钞诡,這世上最難降的妖魔是什么郑现? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任湃崩,我火速辦了婚禮,結果婚禮上懂酱,老公的妹妹穿的比我還像新娘竹习。我一直安慰自己,他們只是感情好列牺,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布整陌。 她就那樣靜靜地躺著,像睡著了一般瞎领。 火紅的嫁衣襯著肌膚如雪泌辫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天九默,我揣著相機與錄音震放,去河邊找鬼。 笑死驼修,一個胖子當著我的面吹牛殿遂,可吹牛的內容都是我干的。 我是一名探鬼主播乙各,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼墨礁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了耳峦?” 一聲冷哼從身側響起恩静,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹲坷,沒想到半個月后驶乾,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡循签,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年级乐,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片县匠。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡唇牧,死狀恐怖,靈堂內的尸體忽然破棺而出聚唐,到底是詐尸還是另有隱情丐重,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布杆查,位于F島的核電站扮惦,受9級特大地震影響,放射性物質發(fā)生泄漏亲桦。R本人自食惡果不足惜崖蜜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一浊仆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豫领,春花似錦抡柿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至课蔬,卻和暖如春囱稽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背二跋。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工战惊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扎即。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓吞获,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谚鄙。 傳聞我的和親對象是個殘疾皇子各拷,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350