UISearchResultsUpdating w/ ReactiveCocoa

UISearchResultsUpdating w/ ReactiveCocoa

我正在构建一个 UISearchController,用户将在其中键入用户名,应用程序将从 Web 服务获取结果。

我想限制请求以减少用户键入时的网络调用。使用 ReactiveCocoa 如何实现这一点?

class SearchResultsUpdater: NSObject, UISearchResultsUpdating {
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        let text = searchController.searchBar.text
        let dataSource = searchResultsController.tableView.dataSource as! ...     
    }
}

抱歉,我对 RAC Swift API 不是很熟悉,但这可以在 Objective-C 版本的 RAC 中通过调用 bufferWithTime:onScheduler: 方法来实现一个 RACSignal,所以它无疑会有一个 Swift 对应。

示例:

double sampleRate = 2.0;
[[textField.rac_textSignal bufferWithTime:sampleRate onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(RACTuple * x) {
    NSLog(@"%@", x.last); //Prints the latest string in the tuple.
}];

将此与 UISearchController 合并:

double sampleRate = 2.0;
[[[self rac_signalForSelector:@selector(searchBar:textDidChange:) fromProtocol:@protocol(UISearchBarDelegate)] 
bufferWithTime:sampleRate onScheduler:[RACScheduler mainThreadScheduler]] 
subscribeNext:^id(RACTuple * x) {
    NSLog(@"%@", x.last);
}];

Here's 一篇博客 post 关于给 UISearchController 一个 rac_textSignal 选项,这样你就不必自己实现委托函数,而使用上面的代码,你SearchResultsUpdater.

中仍然需要一个空的 searchBar:textDidChange: 函数

这可能不是您要查找的内容,但它可能会帮助您实现目标。我在这个要点中使用了 NSTimer 扩展:https://gist.github.com/natecook1000/b0285b518576b22c4dc8

let (keySignal, keySink) = Signal<String, NoError>.pipe()

func createIntervalSignal(interval: Double) -> Signal<(), NoError> {
    return Signal {
        sink in
        NSTimer.schedule(repeatInterval: interval) { timer in
            sendNext(sink, ())
        }
        return nil
    }
}

func textFieldChanged(sender:UITextField) {
    sendNext(keySink, sender.text)
}

let sendNetworkRequestSignal = keySignal |> sampleOn(createIntervalSignal(1.0))
let disposeThis = sendNetworkRequestSignal |> observe(next: { stringVal in  }) //send requests in the closure

实际上是用 ReactiveCocoa 解决这个问题的好方法。

您想在用户键入时获取文本,或者在 'Reactive' 中您想要输入文本的“流”字,如果您的用户是快速打字者,您希望仅在以下情况下才向服务器执行请求搜索文本在短时间内是不变的,你可以自己做(使用 Delegate,NSTimer),但是 ReactiveCocoa 真的很简单和可读。

@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

[[textSignal throttle:0.4]subscribeNext:^(NSString* searchText) {
    [[SearchService sharedInstance]search:searchText completed:^(NSString *searchResult, NSError *error) {
       NSLog(@"searchResult: %@",searchResult);
    }];
}];

假设您的 class SearchService return 是 2.5 秒后的 searchText 和 searchText 长度。

 @implementation SearchService

typedef void(^CompletedResults)(NSString *searchResult, NSError *error);

- (void)search:(NSString *)text completed:(CompletedResults)handler {

    NSString *retVal = [NSString stringWithFormat:@"%@ = %@", text, @([text length])];

    // Here you should to do your network call and and return the response string
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2.5];
        if (handler){
            handler(retVal, nil);
        }
    });
}

只需一行代码,您就可以限制输入文本。

其实ReactiveCocoa并没有提供UISearchBar的类,但是实现起来也没有那么复杂(你可以找UISearchBar(RAC)类hire

你想问自己的重要一件事是,如果你已经向服务器发送请求并且在你得到答案之前用户继续输入会发生什么? 您可能想取消先前的请求(并释放所有资源)并使用新的搜索文本向服务器发送新请求。 同样,你可以自己做,但使用 ReactiveCocoa 非常简单,如果你只是开始将事物视为信号。

您应该包装来自服务器的 return "stream" 个结果的搜索服务。

@implementation SearchService

- (RACSignal *)search:(NSString *)text {

    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

        [self search:text completed:^(NSString *searchResult, NSError *error) {
            [subscriber sendNext:searchResult];
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}

现在您所要做的就是将每个搜索文本映射到来自服务器的结果信号并调用 switchToLatest。

[[[[textSignal throttle:0.4]
map:^id(NSString* searchText) {
    return [[SearchService sharedInstance]search:searchText];
}]
switchToLatest]
subscribeNext:^(NSString* searchResult) {
    NSLog(@"searchResult: %@",searchResult);
}];

还有一件事,可能是当您从服务器获得响应时要更新 UI。而且你必须在主线程上进行。 同样在这里使用 ReactiveCocoa 非常简单,只需添加 deliverOn:RACScheduler.mainThreadScheduler.

[[[[[textSignal throttle:0.4]
map:^id(NSString* searchText) {
     NSLog(@"Get Text after throttle");
    return [[SearchService sharedInstance]search:searchText];
}]
switchToLatest]
deliverOn:RACScheduler.mainThreadScheduler]
subscribeNext:^(NSString* searchResult) {

    if ([NSThread isMainThread]){
        NSLog(@"is MainThread");
    }
    else{
         NSLog(@"is not MainThread");
    }
    NSLog(@"searchResult: %@",searchResult);
}];

祝你好运:)

如果您使用 Swift 编写代码,请查看 ReactiveSwift GitHub - Swift 的响应式扩展,受 ReactiveCocoa 启发