ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
Get to grips with ReactiveCocoa in this 2-part tutorial series. Put the paradigms to one-side, and understand the practical value with work-through examples By Colin Eberhardt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
40 mins
Asynchronous Loading of Images
You’ve probably noticed there is a gap to the left of each tweet. That space is there to show the Twitter user’s avatar.
The RWTweet
class already has a profileImageUrl
property that is populated with a suitable URL for fetching this image. In order for the table view to scroll smoothly, you need to ensure the code that fetches this image from the given URL is not executed on the main thread. This can be achieved using Grand Central Dispatch or NSOperationQueue. But why not use ReactiveCocoa?
Open RWSearchResultsViewController.m and add the following method to the end of the file:
-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {
RACScheduler *scheduler = [RACScheduler
schedulerWithPriority:RACSchedulerPriorityBackground];
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
UIImage *image = [UIImage imageWithData:data];
[subscriber sendNext:image];
[subscriber sendCompleted];
return nil;
}] subscribeOn:scheduler];
}
You should be pretty familiar with this pattern by now!
The above method first obtains a background scheduler as you want this signal to execute on a thread other than the main one. Next, it creates a signal that downloads the image data and creates a UIImage
when it has a subscriber. The final piece of magic is subscribeOn:
, which ensures that the signal executes on the given scheduler.
Magic!
Now, within the same file update the tableView:cellForRowAtIndex:
method by adding the following just before the return
statement:
cell.twitterAvatarView.image = nil;
[[[self signalForLoadingImage:tweet.profileImageUrl]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];
The above first resets the image since these cells are reused and could therefore contain stale data. Then it creates the required signal to fetch the image data. The deliverOn:
pipeline step, you encountered previously, marshals the next event onto the main thread so that the subscribeNext:
block can be safely executed.
Nice and simple!
Build and run to see that the avatars now display correctly:
Throttling
You might have noticed that every time you type a new character, a Twitter search executes immediately. If you’re a fast typer (or simply hold down the delete key), this can result in the application performing several searches a second. This is not ideal for a couple of reasons: firstly, you’re hammering the Twitter search API and simultaneously throwing away most of the results. Secondly, you’re constantly updating the results which is rather distracting for the user!
A better approach would be to perform a search only if the search text is unchanged for a short amount of time, say 500 milliseconds.
As you’ve probably guessed, ReactiveCocoa makes this task incredibly simple!
Open RWSearchFormViewController.m and update the pipeline at the end of viewDidLoad
by adding a throttle step just after the filter:
[[[[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
throttle:0.5]
flattenMap:^RACStream *(NSString *text) {
@strongify(self)
return [self signalForSearchWithText:text];
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(NSDictionary *jsonSearchResult) {
NSArray *statuses = jsonSearchResult[@"statuses"];
NSArray *tweets = [statuses linq_select:^id(id tweet) {
return [RWTweet tweetWithStatus:tweet];
}];
[self.resultsViewController displayTweets:tweets];
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
The throttle
operation will only send a next event if another next event isn’t received within the given time period. It’s really that simple!
Build and run to confirm that the search results only update if you stop typing for 500 milliseconds. Feels much better doesn’t it? Your users will think so too.
And…with that final step your Twitter Instant application is complete. Give yourself a pat on the back and do a happy dance.
If you got lost somewhere in the tutorial you can download the final project (Don’t forget to run pod install
from the project’s directory before opening), or you can obtain the code from GitHub where there is a commit for each Build & Run step in this tutorial.
Wrap Up
Before heading off and treating yourself to a victory cup of coffee, it’s worth admiring the final application pipeline:
That’s quite a complicated data flow, all expressed concisely as a single reactive pipeline. It’s a beautiful sight to see! Can you imagine how much more complex this application would be using non-reactive techniques? And how much harder it would be to see the data flows in such an application? Sounds very cumbersome, and now you don’t have to go down that road ever again!
Now you know that ReactiveCocoa is really quite awesome!
One final point, ReactiveCocoa makes it possible to use the Model View ViewModel, or the MVVM design pattern, which provides better separation of application logic and view logic. If anyone is interested in a follow-up article on MVVM with ReactiveCocoa, please let me know in the comments. I’d love to hear your thoughts and experiences!