Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
/* The rules outlined in Comparing Reactive and Traditional represent the business logic
for contacting the server: coalesce requests over a timeout period,
coalesce non-unique consecutive requests, and ignore requests shorter
than a specified length. If I’ve learnt anything in nearly 30 years
of writing software, it’s you don’t want to put business logic in the UI.
Working with UI is complicated enough without embedding your business logic there.
That’s why the business logic is embedded in the Fetcher object
– mostly in the -fetchQuery:error: method.
Because we’re coalescing calls, having a method with a completion handler
isn’t appropriate. One unifying theme in Apple’s use of completion handlers
is they are ALWAYS called – either with a result or an error – the block is never just ignored.
Because we plan to ignore many calls to our query method based on the business logic,
either a property with a handler block or a delegate is appropriate.
I chose a delegate, because they still have slightly more historical precedence.
I inferred from Brent’s implementation the rule that new queries should cancel
incomplete queries. I’m not certain whether that’s correct, but it seemed appropriate
to prevent responses coming out of order.*/
#import "ViewController.h"
#import "Fetcher.h"
@interface ViewController () <UITableViewDataSource, UITableViewDelegate, FetcherDelegate>
@property (nonatomic, strong) IBOutlet UITextField *queryField;
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@property (nonatomic, strong) NSMutableArray<FetcherResult *> *messages;
@end
@implementation ViewController
- (void)viewDidLoad
{
self.messages = [NSMutableArray array];
[super viewDidLoad];
[self.queryField addTarget:self action:@selector(queryFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.fetcher.delegate = self;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
}
- (IBAction)refreshTable:(id)sender
{
// clear the messages list and reload the query
self.messages = [NSMutableArray array];
[self.tableView reloadData];
[self.fetcher reset];
NSString *query = self.queryField.text ?: @"";
// Probably shouldn't ignore the result or error here…
[self.fetcher fetchQuery:query error:NULL];
}
- (void)queryFieldDidChange:(id)sender
{
NSString *query = self.queryField.text ?: @"";
// Probably shouldn't ignore the result or error here…
[self.fetcher fetchQuery:query error:NULL];
}
#pragma mark - FetcherDelegate
- (void)fetcher:(Fetcher *)fetcher didReceiveResult:(nullable FetcherResult *)result forQuery:(NSString *)query error:(nullable NSError *)error
{
// We're not guaranteed to be on the main queue…
dispatch_async(dispatch_get_main_queue(), ^{
if (error)
{
// We shouldn't ignore errors…
return;
}
if (!result)
{
// Not certain how we got here without a result…
return;
}
NSInteger newRow = self.messages.count;
[self.messages addObject:result];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:newRow inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
});
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.messages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
FetcherResult *result = self.messages[indexPath.row];
cell.textLabel.text = result.text;
return cell;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment