ReactiveCocoa
发布日期:2022-03-18 08:27:43 浏览次数:57 分类:技术文章

本文共 10934 字,大约阅读时间需要 36 分钟。

学习之前,要先了解下什么是响应式编程和函数式编程:

另外注意

RAC 5.0 相比于 4.0 有了巨大的变化,不仅是受 swift 3.0 大升级的影响,RAC 对自身项目结构的也进行了大幅度的调整。这个调整就是将 RAC 拆分为四个库:ReactiveCocoa、ReactiveSwift、ReactiveObjC、ReactiveObjCBridge。

在项目里现在到底要引入哪些
如果你只是纯 swift 项目,你继续使用 ReactiveCocoa 。但是 RAC 依赖于 ReactiveSwift ,等于你引入了两个库。
如果你的项目是纯 OC 项目,你需要使用的是 ReactiveObjC 。这个库里面包含原来 RAC 2 的全部代码。
如果你的项目是 swift 和 OC 混编,你需要同时引用 ReactiveCocoa 和 ReactiveObjCBridge 。但是 ReactiveObjCBridge 依赖于 ReactiveObjC ,所以你就等于引入了 4 个库。

官方文档的翻译

为什么使用ReactiveCocoa,参考

作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。ReactiveCocoa为事件定义了一个标准接口,从而可以使用一些基本工具来更容易的连接、过滤和组合。

首先是对SignalSubscriber的理解,在中有很好的解释:

这是RAC最核心的内容,这里我想用插头和插座来描述,插座是Signal,插头是Subscriber。想象某个遥远的星球,他们的电像某种物质一样被集中存储,且很珍贵。插座负责去获取电,插头负责使用电,而且一个插座可以插任意数量的插头。当一个插座(Signal)没有插头(Subscriber)时什么也不干,也就是处于冷(Cold)的状态,只有插了插头时才会去获取,这个时候就处于热(Hot)的状态。

Signal获取到数据后,会调用Subscriber的sendNext, sendComplete, sendError方法来传送数据给Subscriber,Subscriber自然也有方法来获取传过来的数据,如:[signal subscribeNext:error:completed]。这样只要没有sendComplete和sendError,新的值就会通过sendNext源源不断地传送过来,举个简单的例子:

[RACObserve(self, username) subscribeNext: ^(NSString *newName){	NSLog(@"newName:%@", newName);}];

RACObserve使用了KVO来监听property的变化,只要username被自己或外部改变,block就会被执行。但不是所有的property都可以被RACObserve,该property必须支持KVO,比如NSURLCachecurrentDiskUsage就不能被RACObserve

Signal是很灵活的,它可以被修改(map),过滤(filter),叠加(combine),串联(chain),这有助于应对更加复杂的情况

ReactiveCocoa 中最重要的信号是 RACSignal 对象,其详细的介绍请参考

如下的方式创建一个RACSignal 对象的一个实例:

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id
_Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"dispose"); }]; }]; [signal subscribeNext:^(id _Nullable x) { NSLog(@"%@", x); }];

RACDisposable字面上可理解为销毁,封装了当订阅行为消失时一些应该做的操作。当订阅者调用了sendError:或者sendCompleted方法时表示订阅行为就消失了,相应的dispose就会执行了,做一些清理操作

一些基本的东西

参考

ReactiveCocoa signal(RACSignal)发送事件流给它的subscriber。目前总共有三种类型的事件:nexterrorcompleted

RACSignal有很多方法可以来订阅不同的事件类型。每个方法都需要至少一个block,当事件发生时就会执行block中的逻辑。在上面的例子中可以看到每次next事件发生时,subscribeNext:方法提供的block都会执行。

ReactiveCocoa框架使用category来为很多基本UIKit控件添加signal。这样你就能给控件添加订阅了,UITextField的rac_textSignal就是这么来的。

RACSignal的每个操作都会返回一个RACsignal,这在术语上叫做连贯接口(fluent interface)。这个功能可以让你直接构建管道,而不用每一步都使用本地变量。

如下新加的map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件:

[[[self.usernameTextField.rac_textSignal  map:^id(NSString*text){    return @(text.length);  }]  filter:^BOOL(NSNumber*length){    return[length integerValue] > 3;  }]  subscribeNext:^(id x){    NSLog(@"%@", x);  }];

RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。

RAC(self.usernameTextField, backgroundColor) =  [validUsernameSignal    map:^id(NSNumber *passwordValid) {     return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];    }];

聚合信号

如下combineLatest:reduce:方法把validUsernameSignalvalidPasswordSignal产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号

RACSignal *signUpActiveSignal =  [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]                    reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid) {                      return @([usernameValid boolValue] && [passwordValid boolValue]);                    }];

merge: 把多个信号合并为一个信号,任何一个信号有新值的时候就会调用

// merge:合并,任何一个信号只要发送值,就能订阅    RACSubject *signalA = [RACSubject subject];    RACSubject *signalB = [RACSubject subject];    RACSignal *signals = [signalA merge:signalB];    [signals subscribeNext:^(id x) {        NSLog(@"%@",x);    }];    [signalA sendNext:@1];    [signalB sendNext:@2];    [signalB sendNext:@3];

按钮事件

使用rac_signalForControlEvents处理按钮的事件

[[self.signInButton   rac_signalForControlEvents:UIControlEventTouchUpInside]   subscribeNext:^(id x) {     NSLog(@"button clicked");   }];

也可以使用RACCommand,参考:

如:

RACSignal *enableSignal = [[RACSignal combineLatest:@[self.userTextField.rac_textSignal, self.pwdTextField.rac_textSignal]] map:^id _Nullable(RACTuple * _Nullable value) {                return @([value[0] length] >0 && [value[1] length] > 6);            }];    self.loginButton.rac_command = [[RACCommand alloc] initWithEnabled:enableSignal signalBlock:^RACSignal * _Nonnull(id  _Nullable input) {        return [RACSignal empty];    }];

异步API用信号封装

-(RACSignal *)signInSignal {  return [RACSignal createSignal:^RACDisposable *(id
subscriber) { [self.signInService signInWithUsername:self.usernameTextField.text password:self.passwordTextField.text complete:^(BOOL success) { [subscriber sendNext:@(success)]; [subscriber sendCompleted]; }]; return nil; }];}

上面的代码使用RACSignal的createSignal:方法来创建信号。方法的入参是一个block,这个block描述了这个信号。当这个信号有subscriber时,block里的代码就会执行。

block的入参是一个subscriber实例,它遵循RACSubscriber协议,协议里有一些方法来产生事件,你可以发送任意数量的next事件,或者用error\complete事件来终止。本例中,信号发送了一个next事件来表示登录是否成功,随后是一个complete事件。

这个block的返回值是一个RACDisposable对象,它允许你在一个订阅被取消时执行一些清理工作。当前的信号不需要执行清理操作,所以返回nil就可以了

then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号

The then method waits until a completed event is emitted, then subscribes to the signal returned by its block parameter. This effectively passes control from one signal to the next.

The then method passes error events through. Therefore the final subscribeNext:error: block still receives errors emitted by the initial access-requesting step.

[[[self requestAccessToTwitterSignal]  then:^RACSignal *{    @strongify(self)    return self.searchText.rac_textSignal;  }]  subscribeNext:^(id x) {    NSLog(@"%@", x);  } error:^(NSError *error) {    NSLog(@"An error occurred: %@", error);  }];

信号中的信号

使用flattenMap处理信号中的信号

[[[self.signInButton  rac_signalForControlEvents:UIControlEventTouchUpInside]  flattenMap:^id(id x) {    return [self signInSignal];  }]  subscribeNext:^(NSNumber *signedIn) {    BOOL success = [signedIn boolValue];    self.signInFailureText.hidden = success;    if (success) {      [self performSegueWithIdentifier:@"signInSuccess" sender:self];    }  }];

map: 用于普通信号,信号发出普通值

FlatternMap和Map的区别:

1.FlatternMap中的Block返回信号。
2.Map中的Block返回对象。
3.开发中,如果信号发出的值不是信号,映射一般使用Map
4.开发中,如果信号发出的值是信号,映射一般使用FlatternMap。

side effect

side effect副作用,在每次订阅信号量的时候,会导致信号量里的代码重复的执行,这些重复执行的过程中可能会产生我们所不需要的东西

如下:

__block int a = 10;        RACSignal *s = [RACSignal createSignal:^RACDisposable * _Nullable(id
_Nonnull subscriber) { NSLog(@"1"); a += 5; [subscriber sendNext:@(a)]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ }]; }]; [s subscribeNext:^(id _Nullable x) { NSLog(@"2 [a is %@]", x); }]; [s subscribeNext:^(id _Nullable x) { NSLog(@"3 [a is %@]", x); }];

两次订阅,其输出结果为:

2017-07-09 17:03:10.266 RAC01[6540:764267] 12017-07-09 17:03:10.267 RAC01[6540:764267] 2 [a is 15]2017-07-09 17:03:10.267 RAC01[6540:764267] 12017-07-09 17:03:10.268 RAC01[6540:764267] 3 [a is 20]

如果不想每次都调用,可添加replayLastreplayLast只记录旧的signal返回的值,而不会将其中的代码再次执行

RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id
_Nonnull subscriber) { NSLog(@"1"); a += 5; [subscriber sendNext:@(a)]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ }]; }] replayLast];

RACSequence

RACSequenceRACSignal一样,也是继承自RACStream,不同的是RACSignal是拉驱动,RACSequence是推驱动,即在初始化的时候,内容已近形成,并不是像RACSignal在你订阅的时候才生成内容,所以RACSequenceRACSignal可以相互转化

RACSignal转为RACSequence

RACSequence *sequence = [s sequence];

RACSequence转为RACSignal

[sequence signal]

参考

RAC(TARGET, [KEYPATH, [NIL_VALUE]]),作用是将一个对象的一个属性和一个signal绑定,signal每产生一个value(id类型),都会自动执行:

[TARGET setValue:value ?: NIL_VALUE forKeyPath:KEYPATH];

RACObserve(TARGET, KEYPATH)作用是观察TARGETKEYPATH属性,相当于KVO,产生一个RACSignal,最常用的使用,和RAC宏绑定属性

RAC(self.outputLabel, text) = RACObserve(self.model, name);

还有如下的宏:

@weakify(Obj);@strongify(Obj);

这对宏在 RACEXTScope.h 中定义,RACFramework好像没有默认引入,需要单独import,他们的作用主要是在block内部管理对self的引用:

@weakify(self); // 定义了一个__weak的self_weak_变量[RACObserve(self, name) subscribeNext:^(NSString *name) {    @strongify(self); // 局域定义了一个__strong的self指针指向self_weak    self.outputLabel.text = name;}];

注意: If you’re interested in finding out what @weakify and @strongify actually do, within Xcode select Product -> Perform Action -> Preprocess “RWSearchForViewController”. This will preprocess the view controller, expand all the macros and allow you to see the final output.

查看预处理之后生成的代码,可查看@weakify和@strongify到底做了什么

RACScheduler

参考

RACScheduler作为调度器,底层只是对GCD的简单的封装

,在主线程上更新UI,使用deliverOn: 方法

deliverOn:[RACScheduler mainThreadScheduler]

异步加载图片

加载图片的信号

-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {    RACScheduler *scheduler = [RACScheduler                         schedulerWithPriority:RACSchedulerPriorityBackground];    return [[RACSignal createSignal:^RACDisposable *(id
subscriber) { NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; UIImage *image = [UIImage imageWithData:data]; [subscriber sendNext:image]; [subscriber sendCompleted]; return nil; }] subscribeOn:scheduler]; }

订阅这个信号:

cell.twitterAvatarView.image = nil;[[[self signalForLoadingImage:tweet.profileImageUrl]  deliverOn:[RACScheduler mainThreadScheduler]]  subscribeNext:^(UIImage *image) {   cell.twitterAvatarView.image = image;  }];

实际运用的例子

获取json数据

- (RACSignal *)signalFromJson:(NSString *)url{    return [RACSignal createSignal:^RACDisposable * _Nullable(id
_Nonnull subscriber) { NSURLSessionConfiguration *configration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configration]; NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { [subscriber sendError:error]; } else{ NSError *e; NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&e]; if (e) { [subscriber sendError:e]; }else{ [subscriber sendNext:jsonDic]; [subscriber sendCompleted]; } } }]; [task resume]; return [RACDisposable disposableWithBlock:^{ }]; }];}...... NSString *url = @"https://jsonplaceholder.typicode.com/posts"; [[self signalFromJson:url] subscribeNext:^(id _Nullable x) { NSLog(@"jsonDic [==%@==]", x); } error:^(NSError * _Nullable error) { NSLog(@"error: %@", error); } completed:^{ NSLog(@"完成"); }];

相关文章:

转载地址:https://windzen.blog.csdn.net/article/details/53610205 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:iOS网络——socket
下一篇:iOS第三方——SMPageControl

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年03月23日 05时42分32秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

mysql获取刚新增的数据库_如何取得刚插入数据库的数据的id mysql 2019-04-21
python将10到1递减_(Python)如何将3个递减列表合并成一个递减列表? 2019-04-21
python脚本怎么用来处理数据_长时间运行数据处理python脚本的程序结构 2019-04-21
python转成c 语言_将Python对象转换为C void类型 2019-04-21
resin mysql_Eclipse+resin+mysql 安装及环境配置 2019-04-21
redis的使用 Java_java中使用redis 2019-04-21
java 数组元素位置_Java – 在数组中获取元素位置 2019-04-21
c 泛型与java泛型_C ++和Java中的“泛型”类型之间有什么区别? 2019-04-21
java 返回实体对象_java 封装返回结果实体类 返回结果以及错误信息 2019-04-21
java web 防止sql注入攻击_JavaWeb防注入知识点(一) 2019-04-21
java ssm 异常分类_SSM项目常见的异常与处理提示(一) 2019-04-21
java定义矩形类_Java定义矩形类 2019-04-21
java变量怎么变常量_Java的常量与变量是什么?怎么学习呀? 2019-04-21
java开发招聘试题_客户化开发招聘试题-Java开发.doc 2019-04-21
java jdk win10 1335_win10下安装java jdk,tomcat 2019-04-21
java list二分查找_java中的ArrayList和LinkedList的二分查找速度比 | 学步园 2019-04-21
php中的变量名称用什么表示,PHP变量,方法,类等名称中的有效字符是什么? 2019-04-21
pic32mx是什么cpu_PIC32MX单片机外设库使用(Ⅰ)- 系统时钟及I/O口基本设置 2019-04-21
用c 在mysql上存图片_C 批量保存图片进 mysql 利用MYSQL_BIND插入longblob 2019-04-21
mysql 1045 28000_mysql报关于用户密码1045(28000),几种处理方法 (zhuan) 2019-04-21