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.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Avoiding Retain Cycles

While ReactiveCocoa does a lot of clever stuff behind the scenes — which means you don’t have to worry too much about the memory management of signals — there is one important memory-related issue you do need to consider.

If you look at the reactive code you just added:

[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    self.searchText.backgroundColor = color;
  }];

The subscribeNext: block uses self in order to obtain a reference to the text field. Blocks capture and retain values from the enclosing scope, therefore if a strong reference exists between self and this signal, it will result in a retain cycle. Whether this matters or not depends on the lifecycle of the self object. If its lifetime is the duration of the application, as is the case here, it doesn’t really matter. But in more complex applications, this is rarely the case.

In order to avoid this potential retain cycle, the Apple documentation for Working With Blocks recommends capturing a weak reference to self. With the current code you can achieve this as follows:

__weak RWSearchFormViewController *bself = self; // Capture the weak reference

[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    bself.searchText.backgroundColor = color;
  }];

In the above code bself is a reference to self that has been marked as __weak in order to make it a weak reference. Notice that the subscribeNext: block now uses the bself variable. This doesn’t look terribly elegant!

The ReactiveCocoa framework inlcudes a little trick you can use in place of the above code. Add the following import to the top of the file:

#import "RACEXTScope.h"

Then replace the above code with the following:

@weakify(self)
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    @strongify(self)
    self.searchText.backgroundColor = color;
  }];

The @weakify and @strongify statements above are macros defined in the Extended Objective-C library, and they are also included in ReactiveCocoa. The @weakify macro allows you to create shadow variables which are weak references (you can pass multiple variables if you require multiple weak references), the @strongify macro allows you to create strong references to variables that were previously passed to @weakify.

Note: If you’re interested in finding out what @weakify and @strongify actually do, within Xcode select Product -> Perform Action -> Preprocess “RWSearchForViewController”. This will preprocess the view controller, expand all the macros and allow you to see the final output.

One final note of caution, take care when using instance variables within blocks. These will also result in the block capturing a strong reference to self. You can turn on a compiler warning to alert you if your code results in this problem. Search for retain within the project’s build settings to find the options indicated below:

AvoidRetainSelf

Okay, you survived the theory, congrats! Now you’re much wiser and ready to move on to the fun part: adding some real functionality to your application!

Note: The keen-eyed readers among you who paid attention in the previous tutorial will have no doubt notice that you can remove the need for the subscribeNext: block in the current pipeline by making use of the RAC macro. If you spotted this, make that change and award yourself a shiny gold star!

Requesting Access to Twitter

You’re going to use the Social Framework in order to allow the TwitterInstant application to search for Tweets, and the Accounts Framework in order to grant access to Twitter. For a more detailed overview of the Social Framework, check out the chapter dedicated to this framework in iOS 6 by Tutorials.

Before you add this code, you need to input your Twitter credentials into the simulator or the iPad you’re running this app on. Open the Settings app and select the Twitter menu option, then add your credentials on the right hand side of the screen:

TwitterCredentials

The starter project already has the required frameworks added, so you just need to import the headers. Within RWSearchFormViewController.m, add the following imports to the top of the file:

#import <Accounts/Accounts.h>
#import <Social/Social.h>

Just beneath the imports add the following enumeration and constant:

typedef NS_ENUM(NSInteger, RWTwitterInstantError) {
    RWTwitterInstantErrorAccessDenied,
    RWTwitterInstantErrorNoTwitterAccounts,
    RWTwitterInstantErrorInvalidResponse
};

static NSString * const RWTwitterInstantDomain = @"TwitterInstant";

You’re going to be using these shortly to identify errors.

Further down the same file, just beneath the existing property declarations, add the following:

@property (strong, nonatomic) ACAccountStore *accountStore;
@property (strong, nonatomic) ACAccountType *twitterAccountType;

The ACAccountsStore class provides access to the various social media accounts your device can connect to, and the ACAccountType class represents a specific type of account.

Further down the same file, add the following to the end of viewDidLoad:

self.accountStore = [[ACAccountStore alloc] init];
self.twitterAccountType = [self.accountStore 
  accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

This creates the accounts store and Twitter account identifier.

When an app requests access to a social media account, the user sees a pop-up. This is an asynchronous operation, hence it is a good candidate for wrapping in a signal in order to use it reactively!

Further down the same file, add the following method:

- (RACSignal *)requestAccessToTwitterSignal {
  
  // 1 - define an error
  NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain
                                             code:RWTwitterInstantErrorAccessDenied
                                         userInfo:nil];
  
  // 2 - create the signal
  @weakify(self)
  return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    // 3 - request access to twitter
    @strongify(self)
    [self.accountStore
       requestAccessToAccountsWithType:self.twitterAccountType
         options:nil
      completion:^(BOOL granted, NSError *error) {
          // 4 - handle the response
          if (!granted) {
            [subscriber sendError:accessError];
          } else {
            [subscriber sendNext:nil];
            [subscriber sendCompleted];
          }
        }];
    return nil;
  }];
}

This method does the following:

  1. An error is defined, which is sent if the user refuses access.
  2. As per the first article, the class method createSignal returns an instance of RACSignal.
  3. Access to Twitter is requested via the account store. At this point, the user will see a prompt asking them to grant this app access to their Twitter accounts.
  4. After the user grants or denies access, the signal events are emitted. If the user grants access, a next event followed by a completed are sent. If the user denies access, an error event is emitted.

If you recall from the first tutorial, a signal can emit three different event types:

  • Next
  • Completed
  • Error

Over a signal’s lifetime, it may emit no events, one or more next events followed by either a completed event or an error event.

Finally, in order to make use of this signal, add the following to the end of viewDidLoad:

[[self requestAccessToTwitterSignal]
  subscribeNext:^(id x) {
    NSLog(@"Access granted");
  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);
  }];

If you build and run, the following prompt should greet you::

RequestAccessToTwitter

If you tap OK, the log message in the subscribeNext: block should appear in the console, whereas, if you tap Don’t Allow, the error block executes and logs the respective message.

The Accounts Framework remembers the decision you made. Therefore to test both paths you need to reset the simulator via the iOS Simulator -> Reset Contents and Settings … menu option. This is a bit of a pain because you also have to re-enter your Twitter credentials!

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.