UISearchResultsUpdating w / ReactiveCocoa

时间:2015-04-30 20:00:19

标签: ios swift reactive-cocoa

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

我希望在用户输入时限制减少网络呼叫的请求。使用ReactiveCocoa如何实现这个?

from itertools import islice, count

group = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
res = {k:v for k,v in enumerate(iter(lambda i=iter(group), c=count(1): list(islice(i, next(c))), []), 1)}
# {1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

3 个答案:

答案 0 :(得分:6)

实际上,使用ReactiveCocoa解决这个问题非常好。

您希望在用户输入时获取文本,或者在输入文本中使用“活动”单词获取文本,如果您的用户是快速专家,则只有在搜索文本未更改时才要对服务器执行请求在很短的时间内,您可以自己完成(使用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);
    }];
}];

假设您的类SearchService在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非常简单。

您应该将从服务器返回结果“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的Reactive扩展,受ReactiveCocoa的启发

答案 1 :(得分:2)

抱歉,我对RAC Swift API不是很熟悉,但是这可以通过在bufferWithTime:onScheduler:上调用RACSignal方法在Objective-C版本的RAC中实现,所以&# 39;毫无疑问会有一个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一篇关于为UISearchController提供rac_textSignal选项的博客文章,这样您就不必自己实现委托功能,而使用上面的代码,您可以我需要在searchBar:textDidChange:中有一个空的SearchResultsUpdater函数。

答案 2 :(得分:1)

这可能不是您想要的,但它可能会帮助您实现目标。我在这个要点中使用了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