使用ReactiveCocoa实现iOS平台响应式编程

2023-11-19

使用ReactiveCocoa实现iOS平台响应式编程

 TIGER | IOS |  10

使用ReactiveCocoa实现iOS平台响应式编程

ReactiveCocoa和响应式编程

在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Programming,响应式编程),在维基百科中有这样一个例子介绍:

在命令式编程环境中,a = b + c 表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。

Excel就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1″的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化 。

而ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践,它是Github的一个开源项目,你可以在这里找到它。

关于FRP和ReactiveCocoa可以去看leezhong的这篇blog,图文并茂,讲的很好。

ReactiveCocoa框架概览

先来看一下leezhong再博文中提到的比喻,让你对有个ReactiveCocoa很好的理解:

可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。

下面我来逐一介绍ReactiveCocoa框架的每个组件

Streams

Streams 表现为RACStream类,可以看做是水管里面流动的一系列玻璃球,它们有顺序的依次通过,在第一个玻璃球没有到达之前,你没法获得第二个玻璃球。
RACStream描述的就是这种线性流动玻璃球的形态,比较抽象,它本身的使用意义并不很大,一般会以signals或者sequences等这些更高层次的表现形态代替。

Signals

Signals 表现为RACSignal类,就是前面提到水龙头,ReactiveCocoa的核心概念就是Signal,它一般表示未来要到达的值,想象玻璃球一个个从水龙头里出来,只有了接收方(subscriber)才能获取到这些玻璃球(value)。

Signal会发送下面三种事件给它的接受方(subscriber),想象成水龙头有个指示灯来汇报它的工作状态,接受方通过-subscribeNext:error:completed:对不同事件作出相应反应

  • next 从水龙头里流出的新玻璃球(value)
  • error 获取新的玻璃球发生了错误,一般要发送一个NSError对象,表明哪里错了
  • completed 全部玻璃球已经顺利抵达,没有更多的玻璃球加入了

一个生命周期的Signal可以发送任意多个“next”事件,和一个“error”或者“completed”事件(当然“error”和“completed”只可能出现一种)

Subjects

subjects 表现为RACSubject类,可以认为是“可变的(mutable)”信号/自定义信号,它是嫁接非RAC代码到Signals世界的桥梁,很有用。嗯。。。 这样讲还是很抽象,举个例子吧:

1
2
3
RACSubject * letters = [ RACSubject subject ] ;
RACSignal * signal = [ letters sendNext : @ "a" ] ;
 

可以看到@"a"只是一个NSString对象,要想在水管里顺利流动,就要借RACSubject的力。

Commands

command 表现为RACCommand类,偷个懒直接举个例子吧,比如一个简单的注册界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     RACSignal * formValid = [ RACSignal
         combineLatest : @ [
             self . userNameField . rac_textSignal ,
             self . emailField . rac_textSignal ,
         ]
         reduce : ^ ( NSString * userName , NSString * email ) {
             return @ ( userName . length & gt ; 0
                     & amp ; & amp ; email . length & gt ; 0 ) ;
         } ] ;
 
   RACCommand * createAccountCommand = [ RACCommand commandWithCanExecuteSignal : formValid ] ;
   RACSignal * networkResults = [ [ [ createAccountCommand
       addSignalBlock : ^ RACSignal * ( id value ) {
           //... 网络交互代码
       } ]
       switchToLatest ]
       deliverOn : [ RACScheduler mainThreadScheduler ] ] ;
 
   // 绑定创建按钮的 UI state 和点击事件
     [ [ self . createButton rac_signalForControlEvents : UIControlEventTouchUpInside ] executeCommand : createAccountCommand ] ;
 

Sequences

sequence 表现为RACSequence类,可以简单看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用。

Schedulers

scheduler 表现为RACScheduler类,类似于GCD,but schedulers support cancellationbut schedulers support cancellation, and always execute serially.

ReactiveCocoa的简单使用

实践出真知,下面就举一些简单的例子,一起看看RAC的使用

Subscription

接收 -subscribeNext: -subscribeError: -subscribeCompleted:

1
2
3
4
5
6
7
RACSignal * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence . signal ;
 
// 依次输出 A B C D…
[ letters subscribeNext : ^ ( NSString * x ) {
     NSLog ( @ "%@" , x ) ;
} ] ;
 

Injecting effects

注入效果 -doNext: -doError: -doCompleted:,看下面注释应该就明白了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__block unsigned subscriptions = 0 ;
 
RACSignal * loggingSignal = [ RACSignal createSignal : ^ RACDisposable * ( id & lt ; RACSubscriber & gt ; subscriber ) {
     subscriptions ++ ;
     [ subscriber sendCompleted ] ;
     return nil ;
} ] ;
 
// 不会输出任何东西
loggingSignal = [ loggingSignal doCompleted : ^ {
     NSLog ( @ "about to complete subscription %u" , subscriptions ) ;
} ] ;
 
// 输出:
// about to complete subscription 1
// subscription 1
[ loggingSignal subscribeCompleted : ^ {
     NSLog ( @ "subscription %u" , subscriptions ) ;
} ] ;
 

Mapping

-map: 映射,可以看做对玻璃球的变换、重新组装

1
2
3
4
5
6
7
RACSequence * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence ;
 
// Contains: AA BB CC DD EE FF GG HH II
RACSequence * mapped = [ letters map : ^ ( NSString * value ) {
     return [ value stringByAppendingString : value ] ;
} ] ;
 

Filtering

-filter: 过滤,不符合要求的玻璃球不允许通过

1
2
3
4
5
6
7
RACSequence * numbers = [ @ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString : @ " " ] . rac_sequence ;
 
// Contains: 2 4 6 8
RACSequence * filtered = [ numbers filter : ^ BOOL ( NSString * value ) {
     return ( value . intValue % 2 ) == 0 ;
} ] ;
 

Concatenating

-concat: 把一个水管拼接到另一个水管之后

1
2
3
4
5
6
RACSequence * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence ;
RACSequence * numbers = [ @ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString : @ " " ] . rac_sequence ;
 
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence * concatenated = [ letters concat : numbers ] ;
 

Flattening

-flatten:

Sequences are concatenated

1
2
3
4
5
6
7
RACSequence * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence ;
RACSequence * numbers = [ @ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString : @ " " ] . rac_sequence ;
RACSequence * sequenceOfSequences = @ [ letters , numbers ] . rac_sequence ;
 
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence * flattened = [ sequenceOfSequences flatten ] ;
 

Signals are merged (merge可以理解成把几个水管的龙头合并成一个,哪个水管中的玻璃球哪个先到先吐哪个玻璃球)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
RACSubject * letters = [ RACSubject subject ] ;
RACSubject * numbers = [ RACSubject subject ] ;
RACSignal * signalOfSignals = [ RACSignal createSignal : ^ RACDisposable * ( id & lt ; RACSubscriber & gt ; subscriber ) {
     [ subscriber sendNext : letters ] ;
     [ subscriber sendNext : numbers ] ;
     [ subscriber sendCompleted ] ;
     return nil ;
} ] ;
 
RACSignal * flattened = [ signalOfSignals flatten ] ;
 
// Outputs: A 1 B C 2
[ flattened subscribeNext : ^ ( NSString * x ) {
     NSLog ( @ "%@" , x ) ;
} ] ;
 
[ letters sendNext : @ "A" ] ;
[ numbers sendNext : @ "1" ] ;
[ letters sendNext : @ "B" ] ;
[ letters sendNext : @ "C" ] ;
[ numbers sendNext : @ "2" ] ;
 

Mapping and flattening

-flattenMap: 先 map 再 flatten

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
RACSequence * numbers = [ @ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString : @ " " ] . rac_sequence ;
 
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence * extended = [ numbers flattenMap : ^ ( NSString * num ) {
     return @ [ num , num ] . rac_sequence ;
} ] ;
 
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence * edited = [ numbers flattenMap : ^ ( NSString * num ) {
     if ( num . intValue % 2 == 0 ) {
         return [ RACSequence empty ] ;
     } else {
         NSString * newNum = [ num stringByAppendingString : @ "_" ] ;
         return [ RACSequence return : newNum ] ;
     }
} ] ;
 
 
 
 
RACSignal * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence . signal ;
 
[ [ letters
     flattenMap : ^ ( NSString * letter ) {
         return [ database saveEntriesForLetter : letter ] ;
     } ]
     subscribeCompleted : ^ {
         NSLog ( @ "All database entries saved successfully." ) ;
     } ] ;
 

Sequencing

-then:

1
2
3
4
5
6
7
8
9
10
11
12
13
RACSignal * letters = [ @ "A B C D E F G H I" componentsSeparatedByString : @ " " ] . rac_sequence . signal ;
 
// 新水龙头只包含: 1 2 3 4 5 6 7 8 9
//
// 但当有接收时,仍会执行旧水龙头doNext的内容,所以也会输出 A B C D E F G H I
RACSignal * sequenced = [ [ letters
     doNext : ^ ( NSString * letter ) {
         NSLog ( @ "%@" , letter ) ;
     } ]
     then : ^ {
         return [ @ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString : @ " " ] . rac_sequence . signal ;
     } ] ;
 

Merging

+merge: 前面在flatten中提到的水龙头的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RACSubject * letters = [ RACSubject subject ] ;
RACSubject * numbers = [ RACSubject subject ] ;
RACSignal * merged = [ RACSignal merge : @ [ letters , numbers ] ] ;
 
// Outputs: A 1 B C 2
[ merged subscribeNext : ^ ( NSString * x ) {
     NSLog ( @ "%@" , x ) ;
} ] ;
 
[ letters sendNext : @ "A" ] ;
[ numbers sendNext : @ "1" ] ;
[ letters sendNext : @ "B" ] ;
[ letters sendNext : @ "C" ] ;
[ numbers sendNext : @ "2" ] ;
 

Combining latest values

+combineLatest: 任何时刻取每个水龙头吐出的最新的那个玻璃球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RACSubject * letters = [ RACSubject subject ] ;
RACSubject * numbers = [ RACSubject subject ] ;
RACSignal * combined = [ RACSignal
     combineLatest : @ [ letters , numbers ]
     reduce : ^ ( NSString * letter , NSString * number ) {
         return [ letter stringByAppendingString : number ] ;
     } ] ;
 
// Outputs: B1 B2 C2 C3
[ combined subscribeNext : ^ ( id x ) {
     NSLog ( @ "%@" , x ) ;
} ] ;
 
[ letters sendNext : @ "A" ] ;
[ letters sendNext : @ "B" ] ;
[ numbers sendNext : @ "1" ] ;
[ numbers sendNext : @ "2" ] ;
[ letters sendNext : @ "C" ] ;
[ numbers sendNext : @ "3" ] ;
 

Switching

-switchToLatest: 取指定的那个水龙头的吐出的最新玻璃球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RACSubject * letters = [ RACSubject subject ] ;
RACSubject * numbers = [ RACSubject subject ] ;
RACSubject * signalOfSignals = [ RACSubject subject ] ;
 
RACSignal * switched = [ signalOfSignals switchToLatest ] ;
 
// Outputs: A B 1 D
[ switched subscribeNext : ^ ( NSString * x ) {
     NSLog ( @ "%@" , x ) ;
} ] ;
 
[ signalOfSignals sendNext : letters ] ;
[ letters sendNext : @ "A" ] ;
[ letters sendNext : @ "B" ] ;
 
[ signalOfSignals sendNext : numbers ] ;
[ letters sendNext : @ "C" ] ;
[ numbers sendNext : @ "1" ] ;
 
[ signalOfSignals sendNext : letters ] ;
[ numbers sendNext : @ "2" ] ;
[ letters sendNext : @ "D" ] ;
 

常用宏
RAC 可以看作某个属性的值与一些信号的联动

1
2
3
4
RAC ( self . submitButton . enabled ) = [ RACSignal combineLatest : @ [ self . usernameField . rac_textSignal , self . passwordField . rac_textSignal ] reduce : ^ id ( NSString * userName , NSString * password ) {
     return @ ( userName . length & gt ; = 6 & amp ; & amp ; password . length & gt ; = 6 ) ;
} ] ;
 

RACObserve 监听属性的改变,使用block的KVO

1
2
3
4
[ RACObserve ( self . textField , text ) subscribeNext : ^ ( NSString * newName ) {
     NSLog ( @ "%@" , newName ) ;
} ] ;
 

UI Event

RAC为系统UI提供了很多category,非常棒,比如UITextView、UITextField文本框的改动rac_textSignal,UIButton的的按下rac_command等等。

最后

有了RAC,可以不用去操心值什么时候到达什么时候改变,只需要简单的进行数据来了之后的步骤就可以了。

说了这么多,在回过头去看leezhong的比喻该文最后总结的关系图,再好好梳理一下吧。我也是初学者,诚惶诚恐的呈上这篇博文,欢迎讨论,如有不正之处欢迎批评指正。

参考

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.mdhttps://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
http://vimeo.com/65637501
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.html http://nshipster.com/reactivecocoa/

iOSReactiveCocoa

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用ReactiveCocoa实现iOS平台响应式编程 的相关文章

随机推荐

  • VSCode关闭vue语法检查

    今天碰到一个这样的错误 Component name School should always be multi word 意思是组件名称 School 应该总是多个单词 解决办法 在vue config js中添加这样一句代码 lintO
  • QT实现动态翻译和语言切换

    QT GUI提供语言动态转换机制并辅以相应的工具方便programmer实现界面的多语言实时动态切换功能 实现语言动态切换的方法 一个注意 五个步骤 一个注意 实现QT工程的语言切换功能的一个关键点是所有的字符串都需要tr修饰符 例如 m
  • 【数据结构初阶】第七节.树和二叉树的基本操作

    作者简介 大家好 我是未央 博客首页 未央 303 系列专栏 Java初阶数据结构 每日一句 人的一生 可以有所作为的时机只有一次 那就是现在 文章目录 前言 一 二叉树的快速构建 二 二叉树的遍历 2 1 前序遍历 2 2 中序遍历 2
  • 从计数器到分频电路

    一 计数器 1 计数器代码 计数器 顾名思义就是在时钟的节拍下进行计数 一个简单的N位计数器的代码如下所示 这个计数器从0计数到2 N 1 共计数了2 N个数 也就是N位计数器 1 module count parameter N 8 2
  • 微信小程序登录弹框问题

    1 getUserInfo 相信刚接触微信小程序开发的人都在想 官方给出的这个是什么意思 我来解释一下吧 还记得我们在最开始使用微信小程序的时候吗 第一次进一个微信小程序的时候会直接弹出来个框 询问我们是否允许哟用户获取信息 微信官方觉得这
  • C++11 之 std::function & std::bind & lambda 表达式

    文章目录 std function std bind lambda 表达式 总结 c 11新增了 std function std bind lambda 表达式等封装使函数调用更加方便 std function 讲 std functio
  • 生活之你为什么不学习

    最近在别人的空间 我看到了八句话 你有什么理由不学习 感觉说得挺有鸡血的 1 你不能把这个世界让给你所鄙视的人 2 成功的速度一定要超过父母老去的速度 3 可怕的不是别人比你优秀 而是比你优秀的人比你还努力 4 我努力的目的是让我的妈妈买东
  • 【华为OD机试python】按身高和体重排队【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 某学校举行运动会 学生们按编号 1 2 3 n 进行标识 现需要按照身高由低到高排列 对身高相同的人 按体重由轻到重排列 对于身高体重都相同的人 维持原有的编号顺序关
  • 最全的交叉编译Makefile讲解

    最近正在搞交叉编译 参考很多博客 学习了一下Makefile的编写 记录一下Makefile内代码是什么意思 代码如下 简单的hello ko的makefile ifneq KERNELRELEASE obj m hello o else
  • [CUDA] 快速入门CUDA(1)-基本了解和HelloWorld

    CUDA基础 文章目录 CUDA基础 1 CUDA简介 2 GPU和CPU架构的不同之处 3 查看GPU硬件信息 4 需要建立的基本概念 5 总结 1 CUDA简介 CUDA的全程是Computer Unified Device Archi
  • 树莓派4B(buster)的源更换为北外(清华)国内源

    树莓派4B buster 的源更换为北外 清华 国内源 1 登陆到树莓派 ssh pi your raspi IP 2 备份源文件 sudo cp etc apt sources list etc apt sources list bak
  • GoogLeNet论文详解

    GoogLeNet 1 Introduction 得益于深度学习的优势和更强大的卷积神经网络的出现 图像分类和目标检测的准确率发生了令人意想不到的进步 在2014年的ILSVRC比赛中 GoogLeNet取得了第一名的成绩 所用模型参数不足
  • 详解如何修改Linux文件权限

    参考 详解如何修改Linux文件权限 Linux文件权限详解 在Linux系统中 可以使用chmod命令来修改文件的权限 该命令用于更改文件或目录的读取 r 写入 w 和执行 x 权限 以下是一些详细的说明和示例 使用数字表示权限 r 读取
  • Golang教程:(十六)结构体

    原文 https golangbot com structs 欢迎来到Golang系列教程的第十六篇 什么是结构体 结构体 struct 是用户自定义的类型 它代表若干字段的集合 有些时候将多个数据看做一个整体要比单独使用这些数据更有意义
  • element el-table render-header自定义复选框

    项目中需要对列表数据进行批量处理 表头增加复选框 并关联列表数据 el table提供解决方法 实现多选非常简单 手动添加一个el table column 设type属性为selection即可 尝试后在我的项目中不适用 于是找到另一种r
  • Redis缓存与数据库双写一致性解决方案

    目录 1 冤孽的诞生 1 1 需求起因 1 2 策略之争 2 标准解决方案 2 1 延时双删策略 2 2 异步更新缓存 基于订阅binlog的同步机制 3 基于binlog订阅实现步骤 3 1 准备材料 3 2 代码实现 1 冤孽的诞生 1
  • python两个二维数组加法_python中利用numpy.array()实现俩个数值列表的对应相加方法...

    python中利用numpy array 实现俩个数值列表的对应相加方法 小编想把用python将列表 1 1 1 1 1 1 1 1 1 1 和 列表 2 2 2 2 2 2 2 2 2 2 对应相加成 3 3 3 3 3 3 3 3 3
  • [嵌入式linux]PCIe 热拔插(rescan)

    linux下可通过 sys bus pci devices 0000 bus number device number function number 目录下的节点进行热拔插操作 板子上电前PCIe插槽有一块NVME的固态硬盘 0 1985
  • SDIO接口协议(MMC SD WIFI GPS 以太网)

    一文搞懂SDIO 曼巴精神传承人的博客 CSDN博客 sdio SDIO协议 觅食小鱼的博客 CSDN博客 sdio协议 传统的SD存储卡只有一排引脚 包括一个3 3V电源VDD 用于默认速度
  • 使用ReactiveCocoa实现iOS平台响应式编程

    使用ReactiveCocoa实现iOS平台响应式编程 TIGER IOS 10 使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前 先要介绍一下FRP Fun