I'm building a UISearchController in which users will type a username, and the application will fetch results from a web service.
I want to throttle the requests to reduce network calls while the user is typing. Using ReactiveCocoa how would one go about implementing this?
class SearchResultsUpdater: NSObject, UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
let text = searchController.searchBar.text
let dataSource = searchResultsController.tableView.dataSource as! ...
}
}
Actually is very good approach to solve this problem with ReactiveCocoa.
You want to get the text while the user is typing or in 'Reactive' word you want “Stream” of input text, If your user is a fast typer you want to perform request to server only if the search text is unchanged for a short amount of time, You can do it yourself (using Delegate, NSTimer), but with ReactiveCocoa is really simple and readable.
@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);
}];
}];
Let's say your class SearchService returns the searchText and searchText length after 2.5 seconds.
@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);
}
});
}
And with just line of code you throttle the input text.
actually ReactiveCocoa doesn't provide a category of UISearchBar, but it's not so complicated to implement (You can find UISearchBar(RAC) category hire)
Important thing you want to ask yourself is, what will happen if you’ve already sent request to the server and before you get the answer the user continue to typing? You probably want to cancel the previous request (and release all the resources) and send a new request to the server with the new search text. again you can do it yourself but with ReactiveCocoa is very simple, if you just start thinking about things as signals.
You should to wrap your search service that return "stream" of result from the server.
@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;
}];
}
Now all you have to do is to map each search text to signal of result from the server and call to switchToLatest.
[[[[textSignal throttle:0.4]
map:^id(NSString* searchText) {
return [[SearchService sharedInstance]search:searchText];
}]
switchToLatest]
subscribeNext:^(NSString* searchResult) {
NSLog(@"searchResult: %@",searchResult);
}];
And one more thing, probably when you get the response from the server you want to update the UI. And you have to do it on the main thread. Also here with ReactiveCocoa it is really simple, just add 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);
}];
Good luck :)
If you write your code with Swift, take a look at ReactiveSwift GitHub - Reactive extensions for Swift, Inspired by ReactiveCocoa