Using objective-c blocks recursively with iOS Twitter API - objective-c

So I'm trying to use the built in Twitter API in iOS 5 to retrieve a list of all the followers for a given user. In all the example documentation I can find, requests are made to the API passing inline blocks to be executed when the request returns, which is fine for most of the simpler stuff, BUT when I'm trying to get ~1000 followers, and the request is returning them paged in sizes ~100, I'm stuck on how to recursively call the request again using the 'next paging address' returned and processed inside the completion block. Here is the code:
- (void)getTwitterFollowers {
// First, we need to obtain the account instance for the user's Twitter account
ACAccountStore *store = [[ACAccountStore alloc] init];
ACAccountType *twitterAccountType =
[store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request access from the user for access to his Twitter accounts
[store requestAccessToAccountsWithType:twitterAccountType
withCompletionHandler:^(BOOL granted, NSError *error) {
if (!granted) {
// The user rejected your request
NSLog(#"User rejected access to his account.");
}
else {
// Grab the available accounts
NSArray *twitterAccounts =
[store accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] > 0) {
// Use the first account for simplicity
ACAccount *account = [twitterAccounts objectAtIndex:0];
// Now make an authenticated request to our endpoint
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"1" forKey:#"include_entities"];
// The endpoint that we wish to call
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/followers.json"];
// Build the request with our parameter
request = [[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
[params release];
// Attach the account object to this request
[request setAccount:account];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (!responseData) {
// inspect the contents of error
FullLog(#"%#", error);
}
else {
NSError *jsonError;
followers = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (followers != nil) {
// THE DATA RETURNED HERE CONTAINS THE NEXT PAGE VALUE NEEDED TO REQUEST THE NEXT 100 FOLLOWERS,
//WHAT IS THE BEST WAY TO USE THIS??
FullLog(#"%#", followers);
}
else {
// inspect the contents of jsonError
FullLog(#"%#", jsonError);
}
}
}];
} // if ([twitterAccounts count] > 0)
} // if (granted)
}];
[store release];
}
Ideally I'd like some way to listen for this data being returned, check for a next page value and if it exists, reuse the code block and append the data returned. I', sure there must be a 'best-practice' way to achieve this, any help would be much appreciated!

To use any block recursively you have to declare it first and define it later. Try this:
__block void (^requestPageBlock)(NSInteger pageNumber) = NULL;
requestPageBlock = [^(NSInteger pageNumber) {
// do request with some calculations
if (nextPageExists) {
requestPageBlock(pageNumber + 1);
}
} copy];
// now call the block for the first page
requestPageBlock(0);

To expand on #Eimantas' answer, your request handler is expecting a specific block signature, so you need a different way to handle the page number.
-(void)getTwitterFollowers {
// set up request...
__block int page = 0;
__block void (^requestHandler)(NSData*, NSHTTPURLResponse*, NSError*) = null;
__block TWRequest* request = [[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
requestHandler = [^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
followers = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (followers != nil) {
// process followers
page++;
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:request.parameters];
// update params with page number
request = [[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:requestHandler];
}
} copy];
// now call the block for the first page
[request performRequestWithHandler:requestHandler];
}

Related

Sending push notification while in background objective c

First of all i am using parse.com to store information.
This code simply opens the Maps app every time this method is run and saves the users location in a server.
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
[request setSource:[MKMapItem mapItemForCurrentLocation]];
[request setDestination:endingItem];
[request setTransportType:MKDirectionsTransportTypeAutomobile];
[request setRequestsAlternateRoutes:YES];
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if ( ! error && [response routes] > 0) {
MKRoute *route = [[response routes] objectAtIndex:0];
//route.distance = The distance
NSLog(#"total %f",route.expectedTravelTime );
int time = ceil(route.expectedTravelTime/60);
self.ETA = [#(time) stringValue];
NSLog(#"test %d",time);
NSLog(#"Total Distance (in Meters) :%0.1f",route.distance/1000);
self.distance = [#(route.distance*4899) stringValue];
// IF decline was pressed, need to fix if it's accepted
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:self.distance forKey:#"dist"];
[PFCloud callFunctionInBackground:#"sendAccepted" withParameters:params block:^(id object, NSError *error) {
if (!error) {
NSLog(#"Success answer sent");
} else {
NSLog(#"Failed to push");
}
}];
}
}];
[endingItem openInMapsWithLaunchOptions:launchOptions];
}
What i noticed is that if Maps application is already open when this method is run then it does not save the users data until i return to the applikation. HOWEVER if i close the Maps application before this method is run the it is always sent to the server.
Now the problem i think is that it obviously takes more time for Maps app to open if it was not opened before hence giving my applikation more time to complete the update. How can i solve this so it will still update the location even if my applikation goes to the background?

Returning an object from inside block within category class method implementation

I have run into a certain problem with my implementation which I don't really know how to solve. Could You please advise.
I'm trying to implement an NSManagedObject category class Photo+Flickr.m with one class method +(void)photoWithFlickrData:inManagedObjectContext:
What I would like to do is download data from Flickr API using NSURLSessionDownloadTask and then create Photo object and insert this new created object into database (if it's not already there). This part works fine.
And at the end I would like to return new created (or object that was found in db) Photo object. And this is where I run into problem. Since I'm using category I can't use instance variables. I can't really find any good solution to get this Photo object from inside this completionHandler block.
My code:
#implementation Photo (Flickr)
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
{
NSString *placeId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_PLACE_ID];
NSURL *urlInfoAboutPlace = [FlickrFetcher URLforInformationAboutPlace:placeId];
NSURLRequest *request = [NSURLRequest requestWithURL:urlInfoAboutPlace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task =
[session downloadTaskWithRequest:request
completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
if(!error) {
NSData *json = [NSData dataWithContentsOfURL:localfile];
NSDictionary *flickrPlaceDictionary = [NSJSONSerialization JSONObjectWithData:json
options:0
error:NULL];
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
// flickr photo unique id
NSString *uniqueId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_ID];
NSFetchRequest *dbRequest = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
dbRequest.predicate = [NSPredicate predicateWithFormat:#"uniqueId = %#", uniqueId];
NSError *error;
NSArray *reqResults = [context executeFetchRequest:dbRequest error:&error];
if (!reqResults || error || [reqResults count] > 1) {
//handle error
} else if ([reqResults count]) {
//object found in db
NSLog(#"object found!");
photo = [reqResults firstObject];
} else {
//no object in db so create a new one
NSLog(#"object not found, creating new one");
photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo"
inManagedObjectContext:context];
//set its properties
photo.uniqueId = uniqueId;
photo.title = [photoDictionary valueForKey:FLICKR_PHOTO_TITLE];
photo.region = [FlickrFetcher extractRegionNameFromPlaceInformation:flickrPlaceDictionary];
NSLog(#"title: %#", photo.title);
NSLog(#"ID: %#", photo.uniqueId);
NSLog(#"region: %#", photo.region);
}
});
}
}];
[task resume];
//how to get Photo *photo object???
//return photo;
}
I would really appreciate any suggestions on how to implement this.
Since you have async operations happening inside your blocks, you'll need to pass a completion handler (block) to your photoWithFlickrData:inManagedObjectContext: method and call it when you have valid photo data.
You'll need to add a new parameter to your method so you can pass in the completion handler. I'd do something like this:
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
withCompletionHandler:(void(^)(Photo *photo))completionHandler
Then, when you have a valid photo object, call completionHandler like so:
completionHandler(photo);
It looks like you'd want to put that at the very end of the block you're passing to dispatch_async:
/* ... */
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
/* ... */
completionHandler(photo);
});
/* ... */
Then, you can call your method like so:
[Photo photoWithFlickrData:photoDictionary
inManagedObjectContext:context
withCompletionHandler:^(Photo* photo) {
/* use your valid photo object here */
}
];
Outside of your block before you call [session downloadTaskWithRequest:.....] define a variable like this
__block Photo *photoObject = nil;
Then inside the block after you finish setting its properties, set
photoObject = photo;
Now you can do whatever you want with the photoObject variable outside of the block.
Check out this Apple developer documentation on Blocks and Variables.

how to deactivate twitter account in iPhone app that used for Integration

I have app where I am doing the Twitter integration.
I am able to successfully tweets.
Now when I want to de-activate, I am using below.
SLRequest *postRequest1 = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST URL:
[NSURL URLWithString:#"https://api.twitter.com/1/account/end_session.json"]
parameters: [NSDictionary dictionaryWithObject:#"" forKey:#"status"]];
Problem is API with 1 is deprecated and I don't find this method in 1.1.
Is there any alternate for this?
Also what I notice is when I try to activate again, the permission is not asked. It directly send tweet. It ask tweet permission for the first time only. Not after de-activate -- Re-activate process. Is this is how Twitter behaves?
Edit 1
Code I use for all tweet is as below...
if ([self userHasAccessToTwitter]) {
ACAccountStore *account = [[ACAccountStore alloc] init];
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request access from the user to access their Twitter account
[account requestAccessToAccountsWithType:accountType options:NULL completion:^(BOOL granted, NSError *error)
{
if (granted == YES)
{
///////////////////////////////////////////////
// SEND TEXT ONLY ///
///////////////////////////////////////////////
[[NSUserDefaults standardUserDefaults] setValue:#"yes" forKey:#"twitterLogin"];
[[NSUserDefaults standardUserDefaults] synchronize];
// Populate array with all available Twitter accounts
NSString *message1 = [NSString stringWithFormat:#"Hi! I am using twitter integration."];
NSArray *arrayOfAccounts = [account accountsWithAccountType:accountType];
if ([arrayOfAccounts count] > 0)
{
//use the first account available
ACAccount *acct = [arrayOfAccounts objectAtIndex:0];
SLRequest *postRequest1 = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:[NSURL URLWithString:#"http://api.twitter.com/1.1/statuses/update.json"] parameters: [NSDictionary dictionaryWithObject:message1 forKey:#"status"]];
[postRequest1 setAccount:acct];//set account
[postRequest1 performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if(error) {
NSLog(#"error == %#", error);
} else {
NSLog(#"good to go -1- %i", [urlResponse statusCode]);
}
}];
}
}
}];
}
end_session in API v1 never worked as it should anyway. Starting from 1.1 this method was removed and there's no alternative method. The user should decide if he wants to revoke your application access to his twitter account. The only thing you could do is to delete the auth_token.

How to process multiple SLRequest to get friends list in twitter?

I m using the following twitter api to get the friends list:
https://api.twitter.com/1.1/friends/list.json?
And using [SLRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) to get the response data, and perform UI Changes and model data changes in performRequestWithHandler block.
But a single request at the maximum retrieves only 20 friends.(if you set the cursor parameter in api to -1).
I can use the cursor parameter of the api to send request to get the next 20 friends and so on until the cursor value is 0.
cursor parameter can be set to the 'next_cursor' parameter in the response data of the previous request.
But i m not aware of how to call another SLRequest with in the performRequestWithHandler of the previous request, until the 'next_cursor' value in response data of previous request is 0.
Can anybody tell me how to get all the friends using SLRequest or using any other way.
Any help is appreciated.
Thank you.
u can call the next request in the request handler just after you get the response of twitter friends.
Sorry for not elaborating. I thought you would understand.
Here is the code.
ACAccountStore *account = [[ACAccountStore alloc] init];
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request access from the user to access their Twitter account
[account requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
// Did user allow us access?
if (granted == YES)
{
// Populate array with all available Twitter accounts
NSArray *arrayOfAccounts = [account accountsWithAccountType:accountType];
// Sanity check
if ([arrayOfAccounts count] > 0)
{
[self postRequest];
}
}
}];
- (void)PostRequest
{
// Keep it simple, use the first account available
ACAccount *acct = [arrayOfAccounts objectAtIndex:0];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
[tempDict setValue:#"Posting video" forKey:#"status"];
// Build a twitter request
SLRequest *postRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:[NSURL URLWithString:#"https://api.twitter.com/1.1/statuses/update.json"] parameters:tempDict];
[postRequest setAccount:acct];
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSLog(#"Twitter response, HTTP response: %i", [urlResponse statusCode]);
NSString *output = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"%#", output);
**// calling this again and again will help you with multiple post request.**
[self postRequest]
}];
}
Similar thing can be done for friend list too.
Hope I helped.

Getting all tweets of a specific twitter account?

I just want to fetch all tweets of an specific twitter account. I want to use below url but don't know how we can get user_id or screen_name of a twitter account, whose tweets we want to fetch.
Resource URL http://api.twitter.com/1/statuses/user_timeline.format Parameters Always specify either an user_id or screen_name when requesting a user timeline.
Does any one have any idea or source code or reference. Any help will be highly appreciable.
I am using following function for getting 200 tweets but it shows in NSLog
HTTP response status: 403 message
Function
- (IBAction)followOnTwitter:(id)sender
{
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
// Get the list of Twitter accounts.
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
// For the sake of brevity, we'll assume there is only one Twitter account present.
// You would ideally ask the user which account they want to tweet from, if there is more than one Twitter account present.
if ([accountsArray count] > 0) {
// Grab the initial Twitter account to tweet from.
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
[tempDict setValue:#"rohit40793982" forKey:#"screen_name"];
//[tempDict setValue:#"true" forKey:#"follow"];
// [tempDict setValue:#"683286" forKey:#"user_id "];
TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.format"]
parameters:tempDict
requestMethod:TWRequestMethodPOST];
[postRequest setAccount:twitterAccount];
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSLog(#"%#",urlResponse);
tweets = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSString *output = [NSString stringWithFormat:#"HTTP response status: %i", [urlResponse statusCode]];
NSLog(#"%#", output);
[self performSelectorOnMainThread:#selector(displayText:) withObject:output waitUntilDone:NO];
}];
}
}
}];
}
I am not receiving array of all tweets.
Your mistake, I think, is with this line
http://api.twitter.com/1/statuses/user_timeline.format
the ".format" is the format you want the response. For example, if you want XML, use
https://api.twitter.com/1/statuses/user_timeline.xml
or for JSON, use
https://api.twitter.com/1/statuses/user_timeline.json
You will also need to append the screen_name as a query. For example, to get all of my tweets as JSON, use
https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=edent
screen_name is basically, the account name. It's the name right after the '#'. Like WOW's screen_name is #Warcraft.
Oh and in fact the .format was the problem. You have to specify one of the 3: atom, json, or xml. (I think that getting the user timeline don't allow xml return, just read the API)
As I'm a Java programmer I'm afraid I can't help any further :/