Accessing the new Twitter API v1.1 with Xcode.

Twitter decommissioned v1 of its API in June 2013. The new version, v1.1, enforces Authentication and Display requirements. Currently my interest is in accessing tweets by certain users or in certain subject areas. Previously this involved the ASIHTTPRequest
methods. I would request twitter data via code such as

NSURL *url = [NSURL URLWithString:feed];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];

where the string ‘feed’ was of the form say,

@”http://search.twitter.com/search.json?q=westernbulldogs%20OR%20from%3Awesternbulldogs”

When the request finished a method then parsed the resulting data as follows:

- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [[NSString alloc] initWithBytes:[[request responseData] bytes] length:[[request responseData] length] encoding:NSUTF8StringEncoding];
NSDictionary *results = [responseString JSONValue];
NSArray *entries = [[NSArray alloc] initWithArray:[results objectForKey:@"results"]];
if (entries == nil) {
NSLog(@"Failed to parse %@", request.url);
} else{
for (NSDictionary *entry in entries) {
// Parse the data into various fields
 ...
}
}
}

The process of accessing tweets is now more involved. The instructions at https://dev.twitter.com/docs/auth/application-only-auth
were quite helpful and I will summarise the steps I took.

Each app needs to be given a consumer key and consumer secret. The developer arranges this by creating and logging into her twitter developer account
at dev.twitter.com and creating a new application at https://dev.twitter.com/apps. Once this is done the key and secret can be viewed by selecting the app.

The first step in setting up a twitter request is to use the key and secret to obtain a ‘bearer token’. The key and secret are first encoded according to RFC 1738. The result is then concatenated into one string with a colon as the separator. Finally this string is then base64 encoded. A bearer token request is then made. The following method does this work (I have of course disguised the key and secret):

-(void)tokenOAuth{
NSString *key = @"xxxxxxxxxxxxxxx";
NSString *rfc1738key = [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *secret = @"yyyyyyyyyyyyyyyyyyyyyyyyyy";
NSString *rfc1738secret = [secret stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *concat = [NSString stringWithFormat:@"%@:%@", rfc1738key, rfc1738secret];
NSString *enc = [[concat dataUsingEncoding:NSUTF8StringEncoding] base64Encoding_xcd]; // base64 encoded value of concat
NSURL *theURL = [NSURL URLWithString:@"https://api.twitter.com/oauth2/token"];
getToken = [NSMutableURLRequest requestWithURL:theURL];
[getToken addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", enc];
[getToken addValue:authValue forHTTPHeaderField:@"Authorization"];
NSString *post = @"grant_type=client_credentials";
NSData *body = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[getToken setHTTPMethod:@"POST"];
[getToken setValue:[NSString stringWithFormat:@"%u", (unsigned int)[body length]] forHTTPHeaderField:@"Content-Length"];
[getToken setHTTPBody:body];
getTokenConnection = [[NSURLConnection alloc] initWithRequest:getToken delegate:self startImmediately:YES];
}

The connectionDidFinishLoading: method detects when the connection has finished. This method then parses the data obtained by the connection and checks to see if a bearer token was sent. If so, we can proceed to request the relevant tweets using the bearer token as the authorisation. The connectionDidFinishLoading code is as follows (tokenData is the data that the connection has provided and is collected in the connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method):

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if(connection == getTokenConnection){
NSString *tokenString = [[NSMutableString alloc] initWithBytes:[tokenData bytes] length:[tokenData length] encoding:NSUTF8StringEncoding];
if (![tokenData length] == 0) {
results = [tokenString JSONValue];
} else{
results = nil;
printf("Error. No data\n");
return;
}
if([results valueForKey:@"access_token"] && [[results valueForKey:@"token_type"] isEqualToString:@"bearer"]){
bearerToken = [[NSString alloc] initWithString:[results valueForKey:@"access_token"]];
[self requestTweets]; // call a method to request relevant tweets.
}
return;
}

I use the requestTweets method to send a request to twitter for tweets. It looks like this:

- (void) requestTweets{
NSString *feed = @"https://api.twitter.com/1.1/search/tweets.json?q=westernbulldogs%20OR%20from%3Awesternbulldogs" // example request url
NSURL *url = [NSURL URLWithString:feed];
twitterrequest = [NSMutableURLRequest requestWithURL:url];
[twitterrequest addValue:[NSString stringWithFormat:@"Bearer %@",bearerToken] forHTTPHeaderField:@"Authorization"];
[twitterrequest setHTTPMethod:@"GET"];
getTweetsConnection = [[NSURLConnection alloc] initWithRequest:twitterrequest delegate:self startImmediately:YES];
return;
}

Again the connectionDidFinishLoading method detects when the request has finished. The data obtained is then parsed within connectionDidFinishLoading
with the following code (receivedData is the data that the getTweetsConnection has provided and is collected in the connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method);

if(connection == getTweetsConnection){
NSString *responseString = [[NSMutableString alloc] initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];
if (![receivedData length] == 0) {
results = [responseString JSONValue];
} else{
printf("Error:no data obtained\n");
return;
}
// We now need to parse the results
NSArray *entries = [[NSArray alloc] initWithArray:[results objectForKey:@"statuses"]];
if (entries == nil) {
NSLog(@"Failed to parse results");
return;
} else{
for (NSDictionary *entry in entries) {
NSDictionary *userDict = [entry valueForKey:@"user"];
NSString *imageLoc = [userDict valueForKey:@"profile_image_url"];
NSURL *url = [NSURL URLWithString:imageLoc];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
NSString *user = [userDict valueForKey:@"screen_name"];
NSString *name = [userDict valueForKey:@"name"];
NSString *tweetid = [userDict valueForKey:@"id"];
NSString *twitterDateString = [entry valueForKey:@"created_at"];
NSString *dateString = [NSDateFormatter dateStringFromTwitterString:twitterDateString];
NSDate *date = [NSDate dateFromInternetDateTimeString:dateString formatHint:DateFormatHintRFC3339];
NSString *text = [entry valueForKey:@"text"];
Tweet *tweet = [[Tweet alloc] initWithProfileImageLocation:imageLoc icon:image user:user name:name tweetid:tweetid date:date text:text];
[tweets addObject:tweet]; // 'tweets' is the array where the tweets are stored.
}
}

As you can see, you need to do more in v1.1 to get the same information. I have left rate limiting and display requirements out of the above to avoid
complicating matters. In short, Twitter limits the number of requests that users can make and an app needs to ensure that users are not making more requests than they should. A possible penalty for not checking this is blacklisting of the app. In addition Twitter now requires apps to display tweets in certain formats as described on the twitter developer site at https://dev.twitter.com/terms/display-requirements.

2 comments
  1. Nick said:

    Thanks! My Twitter API stopped working due to this migration and I was also using ASIHTTP and this is the only post that explained how to easily use NSURLRequest instead (and worked)…

    • Thanks Nick. Glad it worked for you. I appreciate the feedback.

Leave a reply to burrobert Cancel reply