RAC适合结合MVVM架构进行开发
UI -> 数据 正向传递问题
bind UI —> signal —> 事件的回调
总揽
- 信号映射:map与flattenMap
- 信号过滤:filter、ignore、 distinctUntilChanged
- 信号合并: combineLatest、reduce、merge、zipWith
- 信号连接:concat、then
- 信号操作时间:timeout、interval、dely
- 信号取值:take、takeLast、takeUntil
- 信号跳过:skip
- 信号发送顺序:doNext、completed
- 指定信号在哪个线程中运行:subscribeOn
- 获取信号中的信号:switchToLatest
- 信号错误重试:retry、replay
- 常用场景
1. 信号映射:map与flattenMap
1.1 map
map 方法通常用于将一个信号中的数据转换为另一种形式,以便于在后续操作中使用。
// 将响应数据转换为模型对象
// 请求返回的是 Json 格式的数据,将其转换成模型对象
[self.client rac_GET:someURL parameters:nil].map(^id(NSDictionary *json) {
return [[MyModel alloc] initWithDictionary:json];
}).subscribeNext(^(MyModel *model) {
NSLog(@"%@", model);
}, error:^(NSError *error) {
NSLog(@"Error: %@", error);
});
// 对数组中的元素进行转换
//将请求返回的字符串数据数组转换为大写
[self.client rac_GET:someURL parameters:nil].map(^id(NSArray *strings) {
return [strings.rac_sequence map:^id(NSString *string) {
return [string uppercaseString];
}].array;
}).subscribeNext(^(NSArray *upperCaseStrings) {
NSLog(@"%@", upperCaseStrings);
}, error:^(NSError *error) {
NSLog(@"Error: %@", error);
});
// 对字符串进行筛选
// 剔除请求返回的字符串中的 HTML 标签
[self.client rac_GET:someURL parameters:nil].map(^id(NSString *htmlString) {
return [self stripHtmlTagsFrom:htmlString];
}).subscribeNext(^(NSString *text) {
NSLog(@"%@", text);
}, error:^(NSError *error) {
NSLog(@"Error: %@", error);
});
// 对象属性值的转换
// 日期格式转换
RAC(self.dateLabel, text) = [RACObserve(self.event, date) map:^id(NSDate *date) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"EEEE, MMM d, yyyy h:mm a"];
return [formatter stringFromDate:date];
}];
1.2 flattenMap
flattenMap 方法通常用于将一个信号中的数据流转化成一个新的信号对象
// flattenMap使用步骤:
// 1.传入一个block,block类型是返回值RACStream,参数value
// 2.参数value就是源信号的内容,拿到源信号的内容做处理
// 3.包装成RACReturnSignal信号,返回出去。
// flattenMap底层实现:
// 0.flattenMap内部调用bind方法实现的,flattenMap中block的返回值,会作为bind中bindBlock的返回值。
// 1.当订阅绑定信号,就会生成bindBlock。
// 2.当源信号发送内容,就会调用bindBlock(value, *stop)
// 3.调用bindBlock,内部就会调用flattenMap的block,flattenMap的block作用:就是把处理好的数据包装成信号。
// 4.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
// 5.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
[[self.textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
NSLog(@"%@",value);
return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"flattenMap == %@",x);
}];
// 对象属性转换
// 将一个包含 User 对象的信号流转换成一个包含用户名字符串的信号流
RACSignal *usernamesSignal = [RACObserve(self, users)
flattenMap:^RACSignal *(NSArray *users) {
NSMutableArray *usernames = [NSMutableArray array];
for (User *user in users) {
[usernames addObject:user.name];
}
return [RACSignal return:usernames];
}];
// 网络请求
// 使用一个信号流中的 URL 发起一个 GET 请求,返回所有请求数据的信号流
RACSignal *dataSignal = [[self.urlsSignal
flattenMap:^RACSignal *(NSURL *url) {
return [self.client rac_GET:url.absoluteString parameters:nil];
}]
map:^id(NSDictionary *json) {
// 对请求返回的 JSON 数据进行处理
return [self processJsonData:json];
}];
//连接多个文本输入框的信号流,返回一个包含所有输入文本的信号流
RACSignal *allTextSignal = [[RACSignal combineLatest:@[self.textField1.rac_textSignal, self.textField2.rac_textSignal, self.textField3.rac_textSignal]
reduce:^(NSString *text1, NSString *text2, NSString *text3) {
return [NSString stringWithFormat:@"%@%@%@", text1, text2, text3];
}]
flattenMap:^RACSignal *(NSString *text) {
return [RACSignal return:text];
}];
2. 信号过滤:filter、ignore、 distinctUntilChanged
2.1 filter
filter 方法通常用于从一个信号中过滤出满足某些条件的数据
// 过滤信号,使用它可以获取满足条件的信号.
//
// 过滤:每次信号发出,会先执行过滤条件判断.
RACSignal *signal = [self.textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
if (self.textField.text.length>6) {
self.textField.text = [self.textField.text substringToIndex:6];
}
NSLog(@"value == %@",value);
return value.length<6;
}];
[signal subscribeNext:^(id _Nullable x) {
self.textField.text = x;
NSLog(@"x== %@",x);
}];
// 搜索栏实时搜索过滤
// 假设 searchSignal 是一个可以被订阅的搜索结果信号。
RACSignal *searchSignal = [RACSignal empty];
[[searchSignal filter:^BOOL(NSString *text) {
return text.length > 2; // 只搜索长度大于2的文本
}] subscribeNext:^(NSString *filteredText) {
NSLog(@"%@", filteredText);
}];
// 过滤无效点击事件
// 假设 buttonPressedSignal 是一个可以订阅的按钮点击信号。
RACSignal *buttonPressedSignal = [RACSignal empty];
[[buttonPressedSignal filter:^BOOL(UIButton *x) {
return x.enabled; // 仅处理 enabled 状态为 YES 的点击事件
}] subscribeNext:^(UIButton *button) {
NSLog(@"Button clicked: %@", button.titleLabel.text);
}];
// 实现人员列表的筛选
// 假设 personsSignal 是一个可以被订阅的人员信息信号。
RACSignal *personsSignal = [RACSignal empty];
[[personsSignal filter:^BOOL(Person *person) {
return person.age > 30; // 筛选年龄大于 30 岁的人员信息
}] subscribeNext:^(Person *person) {
NSLog(@"Name: %@, Age: %@", person.name, @(person.age));
}];
// 实现人员列表的筛选
// 假设 personsSignal 是一个可以被订阅的人员信息信号。
RACSignal *personsSignal = [RACSignal empty];
[[personsSignal filter:^BOOL(Person *person) {
return person.age > 30; // 筛选年龄大于 30 岁的人员信息
}] subscribeNext:^(Person *person) {
NSLog(@"Name: %@, Age: %@", person.name, @(person.age));
}];
//筛选出数组中指定类型的元素
[[self.arraySignal filter:^BOOL(id value) {
return [value isKindOfClass:[NSString class]];
}]
subscribeNext:^(id value) {
NSLog(@"%@", value);
}];
// 过滤掉满足一定条件的错误信号
[[[self.client rac_GET:@"http://www.example.com" parameters:nil] catchTo:[RACSignal empty]]
filter:^BOOL(id value) {
return ![value isKindOfClass:[NSError class]];
}]
subscribeNext:^(id value) {
NSLog(@"%@", value);
}];
// 筛选出符合特定要求的对象属性
[[RACObserve(self.viewModel, people) filter:^BOOL(NSArray *people) {
return [people filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"age > 30"]].count > 0;
}]
subscribeNext:^(NSArray *people) {
NSLog(@"%@", people);
}];
2.2 ignore
内部调用filter过滤,忽略掉某个值
[[self.textField.rac_textSignal ignore:@"c"] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
}];
2.3 distinctUntilChanged
- (void)testDistinctUntilChanged{
// 当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
// 在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@2];
}
//清除重复输入
- (void)distinctUntilChangedTest1{
//在处理用户输入的场景中,我们可能想保留并显示最近的输入,而忽略连续的重复输入。
//可以使用 distinctUntilChanged 方法过滤掉连续的重复输入
RACSubject *inputSignal = [RACSubject empty];
// 假设 inputSignal 是一个可以订阅的用户输入信号。
[[inputSignal distinctUntilChanged] subscribeNext:^(NSString *text) {
NSLog(@"Input: %@", text);
}];
}
//避免数据请求重复
- (void)distinctUntilChangedTest2{
//从 API 请求数据,并在数据更新时更新界面。
//如果连续多次请求相同的数据,可能会导致在更新 UI 之前浪费大量时间。
//这时可以使用 distinctUntilChanged 方法过滤掉连续的重复数据请求。
//假设 requestSignal 是一个可以被订阅的 API 请求信号。
RACSubject *requestSignal = [RACSubject empty];
[[requestSignal distinctUntilChanged] subscribeNext:^(id data) {
// 更新 UI
}];
}
//过滤连续的点击事件
- (void)distinctUntilChangedTest3{
RACSubject *buttonClickedSignal = [RACSubject empty];
// 假设 buttonClickedSignal 是一个可以订阅的按钮点击信号。
[[buttonClickedSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"Button clicked");
}];
}
3. 信号合并: combineLatestWith、combineLatest:reduce:、merge、zipWith
3.1 combineLatestWith
//将多个信号合并起来,并且拿到各个信号的最新的值
//必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
- (void)testCombineLatest{
RACSignal *signalA = self.textField.rac_textSignal;
RACSignal *signalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
// 合并信号,任何一个信号发送数据,都能监听到.
RACSignal *comSignal = [signalA combineLatestWith:signalB];
[comSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
RACTuple *tup = (RACTuple *)x;
NSLog(@"-- %@",tup.first);
NSLog(@"-- %@",tup.second);
}];
// 底层实现:
// 1.当组合信号被订阅,内部会自动订阅signalA,signalB,必须两个信号都发出内容,才会被触发。
// 2.并且把两个信号组合成元组发出。
//区别于zipWith zipWith两个信号都要有记录,如果记录的信号触发过了 就不在触发
//zipWith 记录每一次信号 combineLatestWith记录最后一次的信号
}
//异步请求多个接口
- (void)combineLatestTest{
RACSignal *task1Signal = [self rac_taskSignal1];
RACSignal *task2Signal = [self rac_taskSignal2];
[[task1Signal combineLatestWith:task2Signal] subscribeNext:^(RACTuple *tuple) {
NSString *task1Result = tuple.first;
NSString *task2Result = tuple.second;
NSLog(@"Task 1 and Task 2 completed, result is: %@, %@", task1Result, task2Result);
}];
}
3.2 combineLatest:reduce:
//可以将多个信号的最新值进行合并,并组成一个元组,最终输出一个由多个值组成的信号。
- (void)testReduce{
RACSignal *signalA = self.textField.rac_textSignal;
RACSignal *signalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^(id value1, id value2){
return [NSString stringWithFormat:@"reduce == %@ %@",value1,value2];
}];
[reduceSignal subscribeNext:^(id _Nullable x) {
NSLog(@"subscribeNext == %@",x);
}];
}
//异步请求多个接口,合并返回
- (void)testReduce2{
RACSignal *combinedSignal = [RACSignal combineLatest:@[task1Signal, task2Signal]
reduce:^(id task1Result, id task2Result) {
return [NSString stringWithFormat:@"%@, %@", task1Result, task2Result];
}];
[combinedSignal subscribeNext:^(RACTuple *x) {
NSLog(@"请求完成 - %@",x);
}];
}
// 使用 combineLatestWith 方法将三个输入框中的最新值进行合并,形成一个元组并输出成一个新的信号。
// 然后使用 reduce 来获取三个输入框中的文本内容,并根据一定的条件判断启用或禁用注册按钮。
- (void)combineLatestReduceTest{
RACSignal *nameSignal = [RACSignal empty];
RACSignal *passwordSignal = [RACSignal empty];
RACSignal *confirmPasswordSignal = [RACSignal empty];
RACSignal *combinedSignal = [RACSignal combineLatest:@[nameSignal, passwordSignal, confirmPasswordSignal]
reduce:^(NSString *name, NSString *password, NSString *confirmPassword) {
return @(name.length > 0 && password.length > 0 && [password isEqualToString:confirmPassword]);
}];
[combinedSignal subscribeNext:^(NSNumber *allFieldsValid) {
self.registerButton.enabled = [allFieldsValid boolValue];
}];
}
3.3 merge
- merge 方法可以将多个信号合并成一个信号
- 按照信号的发送顺序依次发出信号中的值。
- (void)testMerge{
RACSignal *signalA = self.textField.rac_textSignal;
RACSignal *signalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
// 合并信号,任何一个信号发送数据,都能监听到.
RACSignal *mergeSignal = [signalA merge:signalB];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
// 当我们需要同时监听多个网络请求,并在所有请求完成后进行下一步操作时
// 可以使用 merge 方法将多个网络请求的信号合并成一个信号,然后在订阅这个信号进行数据处理。
- (void)mergeTest1{
RACSignal *request1 = [RACSignal empty];
RACSignal *request2 = [RACSignal empty];
RACSignal *request3 = [RACSignal empty];
[[RACSignal merge:@[request1, request2, request3]] subscribeNext:^(id _Nullable x) {
//处理请求完成后的数据
}];
}
// 当我们需要同时监听多个输入框的值,以便根据输入框的值进行下一步操作时,
// 可以使用 merge 方法将多个输入框的信号合并成一个信号,然后在订阅这个信号进行数据处理。
- (void)mergeTest2{
RACSignal *textField1Signal = [RACSignal empty];
RACSignal *textField2Signal = [RACSignal empty];
RACSignal *textField3Signal = [RACSignal empty];
[[RACSignal merge:@[textField1Signal, textField2Signal, textField3Signal]] subscribeNext:^(id x) {
//根据输入框的值进行下一步操作
}];
}
3.4 zipWith
- (void)testZip{
// 底层实现:
// 1.定义压缩信号,内部就会自动订阅signalA,signalB
// 2.每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把最近发出的信号都包装成元组发出。
RACSignal *signalA = self.textField.rac_textSignal;
RACSignal *signalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
// 合并信号,任何一个信号发送数据,都能监听到.
RACSignal *zipSignal = [signalA zipWith:signalB];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
//zipWith 方法将两个信号的值一一对应地组合成一个元组信号
//然后在订阅这个元组信号进行计算和处理。
- (void)testZip1{
RACSignal *textFieldSignal = self.textField.rac_textSignal;
RACSignal *otherSignal = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
[[[RACSignal zip:@[textFieldSignal, otherSignal]] filter:^BOOL(RACTuple * _Nullable value) {
RACTupleUnpack(NSString *string, UIButton *btn) = value;
return string.length && btn.userInteractionEnabled;
}] subscribeNext:^(RACTuple * _Nullable x) {
RACTupleUnpack(NSString *string, UIButton *btn) = x;
NSLog(@"Text Field: %@, UIButton: %@", string, btn);
}];
}
//将多个信号的结果进行变换,形成一个新的信号输出。
//假设我们有三个异步请求 A、B 和 C,需要将三个结果进行变换成一个新的结果,并输出到一个新的信号中
- (void)testZip2{
RACSignal *requestA = [RACSignal empty];
RACSignal *requestB = [RACSignal empty];
RACSignal *requestC = [RACSignal empty];
[[[RACSignal zip:@[requestA, requestB, requestC]] map:^id _Nullable(RACTuple * _Nullable value) {
id resultA = value.first;
id resultB = value.second;
id resultC = value.third;
return [self transformResultsWithA:resultA B:resultB C:resultC];
}] subscribeNext:^(id _Nullable x) {
// 处理变换后的结果
}];;
}
4. 信号连接:concat、then
4.1 concat
- concat 方法用于按顺序连接多个信号,并将它们的值输出到一个新的信号中
- 通常用于处理顺序执行的同步操作
- 注意是同步操作,一个信号一个信号执行。
// 应用场景:读取本地缓存数据 -> 加载网络数据
- (void)testContact{
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"Helios"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"Gavin"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalC = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"YEHHH"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *contactSignal = [[signalA concat:signalB] concat:signalC];
/*
RACSignal *contactSignal = [[[signalA concat:signalB] concat:signalC] flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
//筛选数据
}];
*/
[contactSignal subscribeNext:^(id _Nullable x) {
NSLog(@"执行完成 - %@",x);
}];
}
4.2 then
- then 方法用于连接两个信号,当第一个信号完成后,自动开始订阅第二个信号。
- 可以用于将两个异步任务进行了顺序执行,等待第一个任务完成后,再执行第二个任务。
- (void)testThen{
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"Cooci"];
[subscriber sendCompleted];
return nil;
}] then:^RACSignal * _Nonnull{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"Gavin"];
[subscriber sendCompleted];
return nil;
}];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
}
- (void)thenTest1{
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"helios"];
[subscriber sendCompleted];
return nil;
}];
[[signal then:^RACSignal *{
return [RACSignal return:@(YES)];
}] subscribeNext:^(id x) {
// 处理信号完成后的数据
NSLog(@"-- %@",x);
}];
}
- (void)thenTest2{
RACSignal *task1Signal = [RACSignal return:@"111"];
RACSignal *task2Signal = [RACSignal return:@"222"];
RACSignal *task3Signal = [RACSignal return:@"333"];
[[[task1Signal then:^RACSignal *{
return task2Signal;
}] then:^RACSignal *{
return task3Signal;
}] subscribeNext:^(id x) {
// 处理任务完成后的数据
NSLog(@"---- %@",x);
}];
}
5. 信号操作时间:timeout、interval、dely
5.1 timeout
- timeout 方法用于限制信号的响应时间,如果在指定时间内未能收到信号,则该信号会发送 error 事件。
- 具体来说,timeout 方法将在指定时间内等待信号的产生,如果超出指定时间仍未产生,则会生成一个超时错误信号。
- (void)timeOut{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"Begin request.");
//延迟 3 秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Finish request.");
[subscriber sendNext:@"Request result"];
[subscriber sendCompleted];
});
return nil;
//设置 限制信号的响应时间 2 秒
}] timeout:2 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
NSLog(@"Received value: %@", x);
} error:^(NSError *error) {
NSLog(@"Error occurred: %@", error);
}];
}
5.2 interval
interval用于创建定时器,一般跟
takeUntil配合使用
//创建定时器
[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]
5.3 dely
- 用于延迟执行
- 使用时需要注意信号所在的线程,在主线程中执行延时操作可能会阻塞UI线程
// 延时执行
- (void)delayTest{
[[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{
NSLog(@"延时1秒时间到~");
}];
}
- (void)delayTest2{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Hello World!"];
[subscriber sendCompleted];
return nil;
//延迟两秒执行
}] delay:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
6. 信号取值:take、takeLast、takeUntil
6.1 take
take:可以屏蔽一些值
//去前面几个值---这里take为2 则只拿到前两个值
- (void)take {
RACSubject *subject = [RACSubject subject];
//取前 2 个信号
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
[subject sendNext:@4];
}
6.2 takeLast
takeLast:和take的用法一样,不过他取的是最后的几个值
//注意点:takeLast 一定要调用sendCompleted,告诉他发送完成了,这样才能取到最后的几个值
- (void)takeLast {
RACSubject *subject = [RACSubject subject];
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
[subject sendCompleted];
}
6.3 takeUntil
- 用于限制当前 signal 的发送,在某个指定信号发送之前
- 给takeUntil传的是哪个signal,那么当这个signal发送信号,就不能再接受原信号的内容了。
- (void)takeUntil {
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
RACSubject *signalC = [RACSubject subject];
[[signalA takeUntil:signalB] subscribeNext:^(id x) {
NSLog(@"Received value from signalA: %@", x);
}];
[[signalB takeUntil:signalC] subscribeNext:^(id x) {
NSLog(@"Received value from signalB: %@", x);
}];
[[signalC takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
NSLog(@"Received value from signalC: %@", x);
}];
[signalA sendNext:@"1"];
[signalA sendNext:@"2"];
[signalA sendCompleted]; //加上sendCompleted之后,下面 3 就不打印了
[signalA sendNext:@"3"];
[signalB sendNext:@"4"];
[signalB sendNext:@"5"];
[signalB sendNext:@"6"];
[signalA sendNext:@"333"]; //不打印
[signalC sendNext:@"7"];
[signalC sendNext:@"8"];
[signalC sendNext:@"9"];
[signalB sendNext:@"666"]; //不打印
}
//使用场景:
// 1. 处理用户交互时
// 使用 rac_textSignal 方法来监听搜索框中的文本变化,然后在搜索框 被释放时 停止接收值。
- (void)setupSearchBox {
@weakify(self);
[[self.searchBox rac_textSignal] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSString *text) {
@strongify(self);
[self searchForText:text];
}];
}
// 2. 界面的自动释放
// 在视图控制器中,在 viewDidLoad 方法中创建一个信号来定时更新 UI,然后在视图控制器被释放时停止这个定时器。
- (void)viewDidLoad {
[super viewDidLoad];
@weakify(self);
[[[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] startWith:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSDate *date) {
@strongify(self);
[self updateUI];
}];
}
// 3. 结束异步调用
// 可以很方便地实现在异步回调过程中取消订阅。
// 例如,在一个网络请求中,我们可以使用 takeUntil 方法在请求完成前取消订阅,避免数据无法及时更新。
- (void)loadData {
@weakify(self);
[[[DataService requestData] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id data) {
@strongify(self);
[self handleData:data];
}];
}
// 4. 限制信号数量
// 可以限制当前信号只发送 n 个值,与 take(n) 方法功能相似。
// 例如,我们可以使用 takeUntil 限制一个 signal 只发送 3 个值,然后停止订阅。
- (void)takeUntilTest1{
RACSubject *signal = [RACSubject subject];
RACSubject *trigger = [RACSubject subject];
[[signal takeUntil:trigger] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送5次信号
for (NSInteger i = 0; i < 3; i++) {
[signal sendNext:@(i)];
}
[trigger sendNext:nil]; // 触发条件信号
[signal sendNext:@(4)]; // 再次发送信号(不会被处理)
}
// 5. 处理多个信号
// 在处理多个信号时,我们可以使用 takeUntil 方法来取消订阅之前的信号,只处理当前的信号。
// 例如,在视图控制器中根据网络请求状态更新 UI 操作时,我们可以使用 takeUntil 方法来处理成功和失败回调信号:
- (void)takeUntilTest2{
@weakify(self);
[[[RACSignal combineLatest:@[successSignal, failureSignal]] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(RACTuple *tuple) {
@strongify(self);
RACTupleUnpack(NSNumber *success, NSError *error) = tuple;
if (success.boolValue) {
[self handleSuccess];
} else {
[self handleError:error];
}
}];
}
// 6. 在处理定时器定时执行任务时
// 我们可以使用 takeUntil 方法在指定的时间点停止执行定时任务。
// 例如,在制定多个定时器协同工作时,我们可以使用 takeUntil 方法在指定时间点停止执行任务:
- (void)takeUntilTest3{
//我们使用 interval 方法创建一个定时器,每个 1 秒触发一次。使用 takeUntil 方法在指定的时间点停止定时器。
NSTimeInterval stopAfterDelay = 2.0; //定义停止定时器时间
[[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]
takeUntil:[RACSignal interval:stopAfterDelay onScheduler:[RACScheduler mainThreadScheduler]]]
subscribeNext:^(id x) {
NSLog(@"excute tasks");
}];
}
// 7. 处理用户行为
// 我们可以使用 takeUntil 方法在某些事件达成后取消订阅。
// 例如,在一个倒计时中,我们可以使用 takeUntil 方法在用户行为发生后停止倒计时。
- (void)takeUntilTest4{
//我们使用 takeUntil 方法获取发送 cancelSignal 信号后停止 countDownSignal 的订阅。
[[self.countDownSignal takeUntil:self.cancelSignal]] subscribeNext:^(id x) {
// 倒计时处理
}];
}
// 8. 定时任务
// 在一个定时器中,我们可以使用 takeUntil 方法在指定时间点之前执行任务。
- (void)takeUntilTest5{
//我们使用 interval 方法每秒执行一个任务,然后使用 takeUntilBlock 方法定义指定时间点计算块,停止任务。
@weakify(self);
[[[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] takeUntilBlock:^BOOL{
@strongify(self);
return self.needStopTimer;
}]] subscribeNext:^(NSDate *date) {
NSLog(@"定时器任务执行中...");
}];
}
// 9. 处理文本操作
// takeUntil 可以很方便地实现在某个特定文本发生时停止订阅。
// 例如,在一个 UITextField 的操作中,我们可以使用 takeUntil 方法在文本内容超过20个字符时停止订阅。
- (void)takeUntilTest5{
@weakify(self);
[[[self.textField rac_textSignal] takeUntilBlock:^BOOL{
@strongify(self);
return (self.textField.text.length > 20);
}]]subscribeNext:^(NSString *text) {
NSLog(@"TextField 中的文本为:%@", text);
}];
}
7. 信号跳过:skip
skip
跳过x个数量的信号
// 表示输入第一次,不会被监听到,跳过第一次发出的信号
- (void)testSkip{
[[self.textField.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"-- %@",x);
}];
}
// 使用 skipUntilBlock 和 skipWhileBlock 方法忽略掉车速达到20公里/小时以下的状态,并在5秒后再次准备开始导航。
- (void)testSkip2{
RACSignal *speedSignal = [RACSignal empty];
RACSignal *navigateSignal = [RACSignal empty];
RACSignal *canNavigateSignal = [[[speedSignal skipUntilBlock:^BOOL(NSNumber *speed) {
return speed.doubleValue > 20.0;
}] skipWhileBlock:^BOOL(NSNumber *speed) {
return speed.doubleValue > 20.0;
}] delay:5.0];
[[navigateSignal takeUntil:canNavigateSignal] subscribeNext:^(id _Nullable x) {
NSLog(@"Start navigation");
}];
}
8. 信号发送顺序:doNext、cocompleted ???
8.1 doNext
使用
doNext:方法来在信号执行next事件时之前,执行指定的 block。
- (void)doNextAction{
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hello"];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 在发送值之前执行某些操作
NSLog(@"%@ is about to be sent", x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
8.2 completed
- 使用
completed方法来监听一个信号的完成事件。- 如果信号发生了错误事件,那么
completed方法将不会执行。
- (void)completedAction{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hello"];
[subscriber sendCompleted];
return nil;
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"print: - %@",x);
}];
// 监听信号的完成事件
[signal subscribeCompleted:^{
NSLog(@"Signal completed");
}];
}
9. 指定信号在哪个线程中运行:subscribeOn
主要用于非常耗时的操作,例如慢接口、复杂数据处理等
- (void)subscribeOnAction{
// 创建一个网络请求信号,使用 subscribeOn 方法在后台线程发起请求
RACSignal *requestSignal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[subscriber sendNext:@"result"];
[subscriber sendCompleted];
});
return nil;
// 使用 subscribeOn: 方法将订阅操作放在后台线程中执行。
// 将最终的信号 replayLast 方法,确保不管订阅的时间点如何,都可以收到发送的最后一个值。
}] subscribeOn:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground]] replayLast];
[requestSignal subscribeNext:^(id x) {
// 在主线程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", x);
});
}];
}
9. 获取信号中的信号:switchToLatest
switchToLatest
switchToLatest 用于将信号中发送的 value 转换为一组新的信号,并订阅最新的信号。
// switchToLatest 将一个 signal-of-signals 转换为最近的 signal,然后订阅此 signal,接收此 signal 发出的所有值。
// 注意switchToLatest:只能用于信号中的信号
- (void)testSwitchToLatest {
RACSubject *signalOfSignal = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
RACSubject *signalC = [RACSubject subject];
[[signalOfSignal switchToLatest] subscribeNext:^(id x) {
NSLog(@"Received value: %@", x);
}];
[signalOfSignal sendNext:signalA];
[signalOfSignal sendNext:signalB];
[signalOfSignal sendNext:signalC];
[signalA sendNext:@1];
[signalA sendNext:@2];
[signalB sendNext:@3];
[signalB sendNext:@4];
[signalC sendNext:@5];
}
10. 信号错误重试:retry、replay
10.1 retry
- retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.
- 用于在信号出现错误时进行重试。retry 方法可以将一个发生错误的 signal 重新执行一次或多次,直到发送了正确的值为止。
- (void)retry{
__block int i = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 10) {
[subscriber sendNext:@1];
}else{
NSLog(@"接收到错误");
[subscriber sendError:nil];
}
i++;
return nil;
//这里可以指定 retry 多少次 注意是从 0 开始计算
}] retry:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
}];
}
10.2 replay
- replay 可以用于缓存信号中的值。
- 具体来说,replay 可以将 signal 视为一个可重放信号,当订阅 signal 时,之前发送过的值会被重新发送。
- 这相当于对 signal 中的值进行了缓存,以便在后续订阅时可以重复使用。以避免重复执行一些开销比较大的操作,可以提高代码的性能和响应速度。
- (void)replay{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"first"];
[subscriber sendNext:@"second"];
[subscriber sendCompleted];
return nil;
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
// 使用 multicast: 方法和 RACReplaySubject 创建重播信号,指定缓存的初始值,底层是NSMutableArray初始化内存大小
RACMulticastConnection *connection = [signal multicast:[RACReplaySubject replaySubjectWithCapacity:2]];
// 开始连接
[connection connect];
//延迟执行
[[RACScheduler mainThreadScheduler] afterDelay:3.0 schedule:^{
//订阅replay信号
[connection.signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}];
}
11. 常用场景
11.1 异步请求,逐个处理
-(void)requestData{
//网络请求1
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求1");
[subscriber sendNext:@"网络请求1"];
[subscriber sendCompleted];
return nil;
}];
//网络请求2
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求2");
[subscriber sendNext:@"网络请求2"];
[subscriber sendCompleted];
return nil;
}];
//异步请求,逐个处理
//按sendNext的顺序,依次回调(哪个接口返回快,就那个接口先回调)
[[RACSignal merge:@[signal1, signal2]] subscribeNext:^(id _Nullable x) {
// 处理请求完成后的数据
}];
11.2 异步请求,合并处理
-(void)requestData{
//网络请求1
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求1");
[subscriber sendNext:@"网络请求1"];
[subscriber sendCompleted];
return nil;
}];
//网络请求2
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求2");
[subscriber sendNext:@"网络请求2"];
[subscriber sendCompleted];
return nil;
}];
[self rac_liftSelector:@selector(dealDataWithData1:data2:data3) signal1,signal2, nil];
}
//三个网络请求结束后在这里处理数据
-(void)dealDataWithData1:(id)data1 data2:(id)data2{
}
11.3 顺序执行网络请求
方案1 concat
将两个信号串起来按顺序执行,每个信号的sendNext会按顺序回调
- (void)testMeht{
//网络请求1
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求1");
[subscriber sendNext:@"网络请求1"];
[subscriber sendCompleted];
return nil;
}];
//网络请求2
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"网络请求2");
[subscriber sendNext:@"网络请求2"];
[subscriber sendCompleted];
return nil;
}];
//将两个信号链接起来,顺序执行
//每个信息执行完成都会回调
RACSignal *contactSig = [signal1 concat:signal2];
[contactSig subscribeNext:^(id _Nullable x) {
DYLog(@"-- %@",x);
}];
}
方案2 then
自动续订下一个信号,最终只回调最后一个sendNext
- (void)thenTest{
//网络请求1
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
//这里可以不要 sendNext 因为没有人订阅,直接sendCompleted就可以
//[subscriber sendNext:@"网络请求1"];
[subscriber sendCompleted];
});
return nil;
//网络请求2
}] then:^RACSignal * _Nonnull{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
[subscriber sendNext:@"网络请求2"];
[subscriber sendCompleted];
});
return nil;
}];
//回调最后一个信号的值
}] subscribeNext:^(id _Nullable x) {
DYLog(@"--- x:%@",x);
}];
}
方案3 Signal + subscribeNext
自动续订下一个信号,最终只回调最后一个sendNext
- (void)requestTest{
//网络请求1
[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
[subscriber sendNext:data];
[subscriber sendCompleted];
});
return nil;
}] subscribeNext:^(id _Nullable x) {
//拿到请求1的值作为请求2的入参
NSString *parm = [x stringValue]
//网络请求2
//...
}];
}
11.4 在后台线程处理耗时操作
- (void)subscribeOnAction{
// 创建一个网络请求信号,使用 subscribeOn 方法在后台线程发起请求
RACSignal *requestSignal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[subscriber sendNext:@"result"];
[subscriber sendCompleted];
});
return nil;
// 使用 subscribeOn: 方法将订阅操作放在后台线程中执行。
// 将最终的信号 replayLast 方法,确保不管订阅的时间点如何,都可以收到发送的最后一个值。
}] subscribeOn:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground]] replayLast];
[requestSignal subscribeNext:^(id x) {
// 在主线程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", x);
});
}];
}
11.5 textField输入限制
- (void)takeUntilTest5{
@weakify(self);
[[[self.textField rac_textSignal] takeUntilBlock:^BOOL{
@strongify(self);
return (self.textField.text.length > 20);
}]]subscribeNext:^(NSString *text) {
NSLog(@"TextField 中的文本为:%@", text);
}];
}
11.6 多个输入框监听
例如提交按钮、下一步按钮的是否可点击判断
//当我们需要同时监听多个输入框的值,以便根据输入框的值进行下一步操作时,
//可以使用 merge 方法将多个输入框的信号合并成一个信号,然后在订阅这个信号进行数据处理。
- (void)mergeTest2{
RACSignal *textField1Signal = [RACSignal empty];
RACSignal *textField2Signal = [RACSignal empty];
RACSignal *textField3Signal = [RACSignal empty];
[[RACSignal merge:@[textField1Signal, textField2Signal, textField3Signal]] subscribeNext:^(id x) {
// 根据输入框的值进行下一步操作
}];
}
11.7 延迟执行
[[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{
}];