ReactiveCocoa 信号操作

这篇文章讲解在ReactiveCocoa中关于Signal的一些常用的操作,涉及到信号的转换操作等。
一.Signal可以通过oberver函数来监听数据流中值的变化

let (signal, observer) = Signal<String, NoError>.pipe()
signal.observe { event in
    switch event {
    case let .value(value):  //当Observer 发送一个value事件的时候 执行
        print("Value: \(value)")
    case let .failed(error):  //当Observer 发送一个Error事件的时候 执行
        print("Failed: \(error)")
    case .completed: // 当Observer 发送Completed事件的时候 执行
        print("Completed")
    case .interrupted: // 当Observer 发送Interrupted事件的时候 执行 
        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将无法继续传递,
//因为它不知道当前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的时候被发送, 当然如果事件流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的时候被发送。

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函数,可以用来结合两个或多个事件流的最新值。结合的事件流中,必须每个事件流都发送了一个值,这时Signal.combineLatest函数产生的新的事件流才会有结果输出。在此之后,任何一个结合的事件流有新值发送时,都会导致结合后的事件流有新值输出。当然,在这过程中 如果有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个元素,这意味着直到每个输入流都发送了第n个值,输出流才会发送第n个值。
zip函数在实际开发中应用比较多,比如某个页面加载完毕以后,需要同时请求两个数据接口,只有当两个数据接口均响应回数据以后,再去刷新页面。这个时候 我们就可以把每个请求数据接口当做一个输入流事件,然后对这两个输入流事件进行zip,当数据响应回来以后发送value事件,这时通过zip后的事件的observer函数 进行监听,刷新页面。

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事件什么时候执行呢?在我看来,
//当某个输入流所输入的值都已经被zip后的输出流所发送,
//然后该输入流发送Completed事件,那么输出流就会触发Completed事件。
//如果该输入流还有value未被输出流所发送,
//那么即使输入流发送Completed事件,输出流也无法触发Completed事件。
//如果大家有不同意见,欢迎评论区提出你的论证。

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操作会将内部事件流的每个值转发给外部事件流。

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同样的道理,将多个内部信号的值拼接成一个单一的数据流,它的拼接策略是当第一个内部信号发送complete以后,外部信号才会接受第二个内部信号发送的值,所以 内部信号的顺序很重要。

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发送的值,
//当lettersSignal发送Completed事件以后,外部信号会依次接收numbersSignal发送的数据
//所以它和数组的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) 操作,从最新的事件流转发值。那么怎么理解这个策略呢,当我们向外部事件流中添加多个内部事件流时,多个内部事件流只有最后一个事件流转发值才会被外部事件流接收。

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发送的数据
numbersObserver.send(value: "1")    // nothing printed
//因为lettersSignal此时为signal中唯一的内部事件流,所以signal会接收到lettersObserver发送的数据
lettersObserver.send(value: "a")    // prints "a"
lettersObserver.send(value: "b")    // prints "b"
numbersObserver.send(value: "2")    // nothing printed
//此时numbersSignal会将signal中lettersSignal内部事件流给替换掉, 
//signal只会接收numbersObserver发送的数据,
//而不会在接收lettersObserver发送的数据. 这就是最新(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。以上内容如有错误或不严谨的地方,欢迎大家在评论区指出问题所在,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343