Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Facebook API - How to cancel Graph Request

I occasionally need to cancel a FaceBook graph request, but there seems to be no cancel or similar method in their API to do so. At the moment, crashes sometimes occur as the delegate I assigned to the request has been deallocated. Is there any way to cancel a graph request once submitted please?

like image 262
CastToInteger Avatar asked Feb 10 '11 15:02

CastToInteger


6 Answers

I'm assuming you're talking about the facebook-ios-sdk project, and the lack of a cancel method in Facebook.h. I noticed this as well, and eventually decided to add my own cancel method. Just to note, the delegate you assign to the request shouldn't ever be dealloc'd and then referenced, because the request retains the delegate. See this similar question. Now, if you find yourself really needing a cancel method for some other reason...

Adding a cancel method:
Facebook requests are made in an opaque manner. You never see them, and only hear about results via the Facebook class. Under the hood, the Facebook class makes Graph API requests with the (not for public use) FBRequest class. This class is is basically a fancy NSURLConnection delegate. So to cancel the request, the member NSURLConnection just has to be told to cancel. Adding this method to FBRequest:

// Add to FBRequest.h
- (void)cancel;

And...

// Add to FBRequest.m
- (void)cancel {
    [_connection cancel];
    [_connection release], _connection = nil;
}

Now, to expose an interface in the Facebook class to make use of the new method...

// Add to Facebook.h
- (void)cancelPendingRequest;  

And...

// Add to Facebook.m
- (void)cancelPendingRequest {
    [_request cancel];
    [_request release], _request = nil;
}

That's all there is to it. The method above will cancel the most recent request, and you'll never hear from it again.

like image 176
Matt Wilding Avatar answered Oct 12 '22 03:10

Matt Wilding


I've followed Matt Wilding's approach listed here, which was very useful, thanks Matt. Unfortunately it didnt quite work for me, so I made some tweaks and now it works... also this revised approach keeps out of the core facebook classes...

//in .h define an FBRequest property
@property (nonatomic, retain) FBRequest * pendingFBRequest;

//in .m when making your request, store it in your FBRequest property
pendingFBRequest = [facebook requestWithGraphPath:@"me/feed"
                                        andParams:params
                                    andHttpMethod:@"POST"
                                      andDelegate:self];

//create a timer for your timeout
pendingFacebookActionTimer = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(onPendingFacebookActionTimeout) userInfo:nil repeats:NO];

//cancel the action on the timeout's selector method
-(void)onPendingFacebookActionTimeout {

    [pendingFBRequest.connection cancel];

}
like image 36
staticfiction Avatar answered Oct 12 '22 02:10

staticfiction


Updated on 22/April/2012

I update Matt's version with the most up-to-date Facebook iOS SDK. My Project is using ARC, but I include the non-ARC Facebook sources so that I can modify the codes. (Of Course, we need to set the "-fno-objc-arc" flag for Facebook source files). The tricky part is to prevent the memory leak, and I think I am doing it correctly. But When I test it in the instrument, I still see very small amount of memory leak. Fortunately, the details show that they are not related to these codes, so I just assume they are related to the app resource handling.

Here is the code I implemented:

// Add to Facebook.h
- (void)cancelPendingRequest:(FBRequest *)releasingRequest; 

And...

// Add to Facebook.m
- (void)cancelPendingRequest:(FBRequest *) releasingRequest{
    [releasingRequest.connection cancel];
    [releasingRequest removeObserver:self forKeyPath:requestFinishedKeyPath];
    [_requests removeObject:releasingRequest];    
}

And in your project which uses FBRequestDelegate

// Declare this member or property to the .h file
FBRequest * currentFbRequest;

// Declare this method
-(void)cancelFBRequest;

And ...

// In .m file
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// prepare your necessary request data and parameter ...
currentFbRequest = [appDelegate.facebook requestWithGraphPath:@"/me/photos" 
    andParams:params 
    andHttpMethod:@"POST" 
    andDelegate:self];



// Then in the method where you want to cancel
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.facebook cancelPendingRequest:currentFbRequest];
currentFbRequest=nil;
like image 30
Wayne Liu Avatar answered Oct 12 '22 03:10

Wayne Liu


For those of us who build the static library and are unable to access the implementation files, a category would be the best way to go.

For those of us who did not build the static library, using a category would be optimal as well because you don't need to modify the existing files.

Here is said category.

// Facebook+Cancel.h
#import "Facebook.h"

@interface Facebook (Facebook_cancel)

- (void)cancelPendingRequest:(FBRequest *)releasingRequest;

- (void)cancelAllRequests;

@end

And then the .m file

// Facebook+Cancel.m
#import "Facebook+Facebook_cancel.h"

@implementation Facebook (Facebook_cancel)

- (void)cancelPendingRequest:(FBRequest *)releasingRequest{
    [releasingRequest.connection cancel];
    if ([_requests containsObject:releasingRequest]) {
        [_requests removeObject:releasingRequest];
        [releasingRequest removeObserver:self forKeyPath:@"state"];

    }
}

- (void)cancelAllRequests {
    for (FBRequest *req in [_requests mutableCopy]) {
        [_requests removeObject:req];
        [req.connection cancel];
        [req removeObserver:self forKeyPath:@"state"];
    }
}

@end

For those using any other answer, you are causing a memory leak. The Facebook SDK will warn you through NSLog that you have not removed an observer. The fourth line in the cancelAllRequests method fixes this problem.

like image 37
Nate Symer Avatar answered Oct 12 '22 03:10

Nate Symer


Try this instead of using NSTimer:

FBRequest *fbRequest = [facebook requestWithGraphPath:@"me" andDelegate:self];
[self performSelector:@selector(fbRequestTimeout:) withObject:fbRequest afterDelay:30];

- (void)fbRequestTimeout:(FBRequest *)fbRequest
{
    [fbRequest.connection cancel];
    [fbRequest setDelegate:nil];
}
like image 32
ozz Avatar answered Oct 12 '22 02:10

ozz


Since SDK 3.1, it's very easy, as startWithCompletionHandler: returns a FBRequestConnection object, which has a -(void)cancel; method.

For example:

// In interface or .h definitions:
@property (strong, nonatomic) FBRequest             *fBRequest;
@property (strong, nonatomic) FBRequestConnection   *fbConnection;

// when needed in class (params should be set elsewhere, this is just an example):
self.fBRequest = [[FBRequest alloc] initWithSession:[FBSession activeSession] graphPath:@"me/photos" parameters:params HTTPMethod:@"POST"];
self.fbConnection = [self.fBRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error){
    NSLog(@"Publish complete, error: %d", error.code);
}];

// now, to cancel anywhere in the class, just call:
[self.fbConnection cancel];
like image 32
Tal Yaniv Avatar answered Oct 12 '22 03:10

Tal Yaniv