浅析ReactiveCocoa原理探究,学会了,真的很好玩,用起来顺心

基础理论

函数响应型编程

FRP是由两种概念组合而来的:
● 响应型编程(Reactive Programming),它关注的是异步数据流,你需要监听并根据其中的数据做出响应。
● 函数式编程(Functional Programming),它的函数定义具有数学风格,计算过程中尽量避免使用变量和状态值,代码更加灵活无副作用。

函数响应型编程实例

case 1

想象有一款应用,需要关注用户的位置变化,并且在发现他的位置靠近一个咖啡店的时候提示他。
通过FRP方式需要这样实现:

  1. 创建一个对象,它发出你需要响应的位置变化事件的数据流
  2. 通过筛选这些位置信息,把那些靠近咖啡店的位置信息显示出来
locationProducer
.filter(ifLocationNearCoffeeShops)
.startWithNext{[weak self] location in
     self?.alertUser(location)
}

● 然后利用函数式编程(Functional Programming)技术去处理这些变化事件的数据。filter函数的用法和数组(array)中的相同,将数据流中的每个值作为参数传给 ifLocationNearCoffeeShops处理,如果返回 true,这个事件会继续传递到下一步。
● 最后,startWithNext就是一个订阅方法,当有(过滤过的)事件传递到这里的时候,你传入的闭包表达式将会执行,并将那些数据作为参数提供给你。
注:上面的代码是异步的,那个过滤方法和闭包表达式只有在位置变化事件发生的时候才会被执行。这体现了函数式编程的精髓,又十分贴切时间轴上的数据的概念。你需要关心的是数据来了,而不是产生这些数据的细节。

case 2 事件转换

上面的例子中,仅仅是关注了位置变化的数据流,除了过滤一些靠近咖啡店的位置,并没有对这些事件做更多的处理。 而FRP范式另一个基本要素,能够将这些事件数据进行组合转换,使其变得更有意义一些 ,可以利用一些高阶函数实现。(map,filter,reduce,combine,zip)
代码优化,过滤掉那些重复的位置信息,并且将传过来的位置数据(CLLocation)转换成一段用户可识别的文本信息—

locationProducer
  .skipRepeats() // 1
  .filter(ifLocationNearCoffeeShops) 
  .map(toHumanReadableLocation) // 2
  .startWithNext {[weak self] readableLocation in
    self?.alertUser(readableLocation)
}

● 首先在 locationProducer发出数据流之后,加一步 skipRepeats操作,这个操作并不是 array所具有的;它是 ReactiveCocoa 所特有的。这个方法的意图很明显:过滤掉那些相等的数据事件(这些事件的数据需要具有可比性)。
● 在 filter方法执行过后,map函数的作用就是将一种事件数据转换成另一种,比如把 CLLocation类型转换成 String类型。

信号

RAC的核心是信号,即RACSignal。信号可以看做是传递数据的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。

冷热信号

● Hot Observable 是主动的,尽管你没有订阅事件,但是它会时刻推送,就像鼠标移动。Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
● Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息。而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
● RACSubject及其子类是热信号。RACSignal排除RACSubject类以外的是冷信号。

想象一下,你需要发起一个网络请求,并且解析它的响应数据,然后展示给用户--


let requestFlow = networkRequest.flatMap(parseResponse)
 
requestFlow.startWithNext {[weak self] result in
  self?.showResult(result)
}

只有当你订阅一个signal(也就是进行startWithNext操作)的时候,网络请求才会被创建并发起。这种signal被称作是冷信号,因为直到你订阅它们之前,它们是出于“冻结”状态的

相反,当订阅一个热信号时,它就已经开始了,所以你可以观察到第三或者第四次事件,或者更多。比较典型的例子就是敲击键盘产生的事件流。

总结一下

● 一个冷信号是指,当你想要订阅他的时候,需要执行开始任务,每个新的订阅者都需要执行开始任务,订阅 requestFlow 三次也就意味着相对应的要创建三个网路请求。
● 一个热信号创建时就已经可以发送事件了,订阅者不需要去开启它。通常 UI 交互是属于热信号。
● ReactiveCocoa 针对热、冷信号分别提供了这两种类型:Signal<T,E> 与 SignalProducer<T,E> 。而RXSwift提供了一种同时支持冷、冷信号的类型:Observable<T>

Q: 为什么要区分冷热信号
A:知道一个信号的含义是很重要的,因为它更好的描述了如何在一个特定的上下文中运用它。当处理一个复杂的系统时,这些将会有很大不同。 假如你正在处理一个热信号,由于某种原因它变成了冷信号,这个时候你将会对每个订阅者进行副作用编程,这将会给你的应用带来很大影响。 实例是,你的应用中,有三个或四个实体需要监听同一种网络请求,而对于每个新的订阅,都会发起一个新的网络请求。

事件类型

这两个框架中,主要有三种事件类型:
● Next<T>:每当一个新的值(T类型)被传到事件流中时,这种事件就会被触发。在上面跟踪定位的那个例子中,T指的就是 CLLocation。
● Compleled:表示事件流的终止。收到这个事件之后,将不会在发送 Next<T>和 Error<E>。
● Error<E>:表示一个错误。在服务器请求的例子中,当你收到一个服务器错误时,这个事件将会被发送。E是遵循了 ErrorType协议的错误类型。收到这个事件之后,将不会在发送 Next<T>和 Compleled。

注:
ReactiveCocoa 的 Singal<T, E>和 SignalProducer<T, E>有两个参数类型,而 RxSwift 的 Observable<T>只有一个。前者的第二个类型(E)是遵循了 ErrorType协议的子类型。在 RxSwift 中这个类型被删除了,取而代之的是一个需要内部处理的 ErrorType协议类型。

常用类

1. RACSignal

信号类,只有当数据变化时,才会发送数据。但是RACSignal自己不具备发送信号能力,而是交给订阅者去发送。默认一个信号发送数据完毕就会自动取消订阅 ,但如果订阅者还在,就不会自动取消信号订阅,因此,如果在实际开发中 需要自己控制订阅者的生命周期 ,可以strong持有,在特定的时机执行dispose方法取消订阅。
RACSignal订阅和发送信号一般过程如下:
● 创建信号 createSignal
RACSignal *single = [RACSignal createSignal:^RACDisposable *(idsubscriber) {}]
● 创建订阅者进行订阅
[single subscribeNext:]
● 发送信号
[subscriber sendNext:]

2. RACSubscriber

订阅者,它不是一个类而是一个协议,实现这个协议的类都称为订阅者

3. RACDisposable

执行订阅取消或者进行对资源的清理工作,dispose

4. RACSubject

是一个继承RACSignal并且遵守RACSubscriber协议的类。所以这一个类不仅可以处理信号,还可以发送信 号。因为RACSubject的subscribeNext方法内部有数组subscribers,可以保存所有的订阅者,而 RACSubject的sendNext在发送信号的时候会遍历所有的订阅者,订阅者执行nextBlock。

5. RACReplaySubject

继承RACSubject,和RACSubject不同之处在于 RACReplaySubject可以先发送信号,然后再订阅信号 。原 因在于RACReplaySubject的subscribe方法中遍历所有的订阅者,拿到当前订阅者发送数据。 RACReplaySubject的sendNext方法是先保存值,然后再发送数据,RACSubject则是直接遍历发送数据。

6. RACMulticastConnection

连接类是为了当我们多次订阅同一个信号的时候,避免订阅信号的block中的代码被调用多次。

7. RACCommand

这个类负责处理事件,可以控制事件的传递以及数据的传递,监控事件的执行过程。

入门语法

UITextField输入文本监听

● 假如有一个UITExtField 文本框,需要对其输入内容进行监听

accountTextField.reactive.continuousTextValues.observeValues { (text) in
          print(text ?? "")
 }

●如果你不想知道TextField文本内容,而是想知道文本长度,可以 使用map函数进行信号内容的修改 ,然后再对map后的信号进行观察。map函数可以对信号的内容进行转换,它的返回值可以是任何你想要的类型。

accountTextField.reactive.continuousTextValues.map { (text) -> Int in
            
            return text!.characters.count
            
        }.observeValues { (count) in
            
            print(count)
            
        }

●也许你只是想在某个条件下,才想监听文本内容,比如当文本的内容大于3的时候。这时可以使用filter函数进行过滤操作。filter函数进行过滤操作。filter函数只返回Bool类型。 只有当filter函数返回true的时候,信号继续传递。我们才能监听到文本的内容 ,当filter函数返回false的时候,信号会被拦截。

accountTextField.reactive.continuousTextValues.filter { (text) -> Bool in
        
                    return text!.characters.count > 3
        
                }.observeValues { (text) in
       
                    print(text ?? "")
        
                }

UIButton 点击事件监听

loginBtn.reactive.controlEvents(.touchUpInside).observeValues { (btn) in
            
            print(btn.currentTitle)
            
        }

Combine Signal 混合信号

let accountSignal = accountTextField.reactive.continuousTextValues.map{
    (text) -> Bool in
    return text!.count > 3
}
let pwdSignal = pwdTextField.reactive.continuousTextValues.map { (text) -> Bool in
            
            return text!.characters.count > 3
        
}

// 使用combineLatest函数将两个信号组合为一个信号,然后利用map函数对信号进行转换,使用 <~将信号的结果绑定到loginBtn
loginBtn.reactive.isEnabled <~ accountSignal.combineLatest(with: pwdSignal).map({ 
        $0 && $1
})
//此方法等价于--
loginBtn.reactive.isEnabled <~ accountSignal.combineLatest(with: pwdSignal).map({ (accountValid, pwdValid) -> Bool in
            return accountValid && pwdValid
        })

MutableProperty

此方法用于对某个属性的value 进行观察—

//<>里面可以是任意类型,它代表属性的类型。
let racValue = MutableProperty<Int>(1)
        
racValue.producer.startWithValues { (make) in
           print(make)
   }
       
racValue.value = 10

方法调用拦截
当你想获取到某个方法被调用的事件,比如UIViewController的ViewWillAppear事件

 self.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:))).observeValues { () in
            
            print("viewWillAppear被调用了")
            
        }

监听对象的生命周期

如果你想在某个对象被销毁以后,做一些事情。那么你可以对这个对象的生命周期进行监听,也就是当对象销毁的时候,你获得对象销毁的信号,然后观察这个信号。当然你也可以重写deinit函数。

var textF : UITextField?

 override func viewDidLoad() {
        super.viewDidLoad()
        textF = UITextField()
        print(textF!)
        textF!.reactive.lifetime.ended.observeCompleted{
            print("销毁")
        }
        textF = UITextField()
        print(textF!)
 }
/*
<UITextField: 0x7fded1014800; frame = (0 0; 0 0); text = ''; opaque = NO; layer = <CALayer: 0x600002ceb3c0>>
<UITextField: 0x7fded1011000; frame = (0 0; 0 0); text = ''; opaque = NO; layer = <CALayer: 0x600002ceb620>>
销毁
*/

开始的时候给textF属性赋值一个对象,然后对该对象的生命周期进行监听。然后给textF属性重新赋值一个另一个内存地址对象。因为textF指针指向了别处,所以这个时候原先内存的引用计数变为0,所以原先的对象会被销毁,而我们正好对原先内存对象的生命周期进行了监听,所以会获取对象被销毁事件。

青山不改,绿水长流,谢谢大家,希望对你有所帮助!

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

推荐阅读更多精彩内容

  • 1.ReactiveCocoa简介 ReactiveCocoa(简称RAC),是由Github开源的一个应用于IO...
    雷晏阅读 1,171评论 4 15
  • 1.ReactiveCocoa简介 ReactiveCocoa(简称为RAC),是由Github开源的一个应用于i...
    F麦子阅读 636评论 0 0
  • 函数响应式编程1:优点RAC虽然最大的优点是提供了一个单一的、统一的方法去处理异步的行为,包括delegate方法...
    SwordDevil阅读 1,234评论 0 4
  • ReactiveCocoa函数响应式编程:FRP 参考文献:http://www.cnblogs.com/taox...
    _Weak阅读 463评论 0 0
  • 1.ReactiveCocoa简介 ReactiveCocoa(简称为RAC),是由Github开源的一个应用于i...
    清蘂翅膀的技术阅读 1,988评论 0 1