使用ReactiveCocoa优化实时搜索

需求

在搜索中输入任何文字,立即联想到相关的搜索关键词,以列表的方式进行显示

最简单的解决方案步骤:

  1. 监听输入框文字的变化.
  2. 在回调中发起网络请求
  3. 将请求的结果显示出来

问题

  1. 在用户输入比较快的情况下,前面几个请求只是在浪费用户的流量,因为请求的结果会立即被覆盖
  2. 由于网络的不确定性,可能后请求的接口要晚于早请求的结果得到返回结果

使用ReactiveCocoa

针对问题1. 解决的办法是限流:

    /// 问题1解决
    self.throttleSubject = [RACSubject subject];
    [[self.throttleSubject throttle:1] subscribeNext:^(id x) {
        [self requestWithKeyWord:x];
    }];
    /// 模拟搜索框输入
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"1"];
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"2"];
    });

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"3"];
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"4"];
    });

打印的结果是3,4
原理:在限流的时间内,如果没有新的数据过来,时间到了,就会sendNext数据,否则,释放上个计时器(计时器也是个信号,通过dispose进行无效化),重新设置一个新的计时器倒计时
针对问题2.

    /// 问题2解决
    self.requestSignal = [RACSubject subject];
    [self.requestSignal.switchToLatest subscribeNext:^(id x) {
        NSLog(@"请求结果 : %@",x);
    }];

原理:switchToLatest这个信号内部会在收到新的信号时候,将上一个信号进行释放,也就是说在A,B两个网络请求信号,按顺序请求的话,如果在B请求前,B请求已经结束,那么,没有任何问题,这时候搜索的内容和关键字肯定还是匹配的,如果B请求的时候,A还没有sendNext|sendComplete那么,会将A信号进行dispose掉,这样即使A信号得到数据了也会return掉

- (void)sendNext:(id)value {
    if (self.disposable.disposed) return;
        .......
}

其实这个时候比较好的网络请求代码如下

- (RACSignal *)requestWithKeyWord:(NSString *)keyword {
    if (keyword.length == 0) return nil;
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
        AFHTTPSessionManager *manager;
        NSURLSessionDataTask *task = [manager POST:nil parameters:nil progress:nil success:nil failure:nil];
        return [RACDisposable disposableWithBlock:^{
            /// 此处可以中断网络请求
            [task cancel];
        }];
    }];
}

这样会使得信号在被新的网络请求信号冲刷掉的时候,及时终止网络请求,节省资源.
完整代码

- (void)testThrottle {
    /// 问题2解决
    self.requestSignal = [RACSubject subject];
    [self.requestSignal.switchToLatest subscribeNext:^(id x) {
        NSLog(@"请求结果 : %@",x);
    }];

    /// 问题1解决
    self.throttleSubject = [RACSubject subject];
    [[self.throttleSubject throttle:1] subscribeNext:^(id x) {
        NSLog(@"keyword : %@",x);
        [self.requestSignal sendNext:[self requestWithKeyWord:x]];
    }];
    
    /// 模拟搜索框输入
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"1"];
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"2"];
    });

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"3"];
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.throttleSubject sendNext:@"4"];
    });
}

- (RACSignal *)requestWithKeyWord:(NSString *)keyword {
    if (keyword.length == 0) return nil;
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:keyword];
        });
        return nil;
    }];
}
keyword : 3
keyword : 4
请求结果 : 4

你可能感兴趣的:(使用ReactiveCocoa优化实时搜索)