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?
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.
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];
}
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;
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.
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];
}
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];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With