RAC系列(2)高级用法

477 阅读19分钟

RAC适合结合MVVM架构进行开发

UI -> 数据 正向传递问题

bind UI —> signal —> 事件的回调

总揽

  1. 信号映射:map与flattenMap
  2. 信号过滤:filter、ignore、 distinctUntilChanged
  3. 信号合并: combineLatest、reduce、merge、zipWith
  4. 信号连接:concat、then
  5. 信号操作时间:timeout、interval、dely
  6. 信号取值:take、takeLast、takeUntil
  7. 信号跳过:skip
  8. 信号发送顺序:doNext、completed
  9. 指定信号在哪个线程中运行:subscribeOn
  10. 获取信号中的信号:switchToLatest
  11. 信号错误重试:retry、replay
  12. 常用场景

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:^{

}];