I have some code which queries facebook for information
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error,) {
if (!error) {
//populate the **mUser** with data
} else {
NSLog(#"Facebook error encountered: %#", error);
}
}];
} else {
NSLog(#"Facebook session is closed");
}
My question is, what is the best way to tell the caller of the function that facebook is finished? I can't simply return from inside the block (incompatible block pointer types).
The calling code looks like this:
myfacey *fb = [[myfacey alloc] init];
[fb getUserFromFacebook: mUser];
//Need to access a populated mUser object here
//calls to mUser result in nil values because facebook hasn't finished
If facebook accessed things synchronously I would have no problem, because mUser would have valid data in it.
Since I have to make asynch calls, what is the best way to signal the calling class that facebook is done populating the variable?
The best way to continue with your program once the asynchronous method has been finished, is putting this code into the completion handler.
Don't try to "return", just "continue" the program in the completion handler.
You should just care about the "execution context" (say thread or dispatch queue) on which the completion handler will be invoked from the async method: if this is not explicitly documented, the completion handler may be called on any thread. So, you might want to dispatch explicitly to the main thread - if necessary:
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error,) {
if (!error) {
// Continue here with your program.
...
// If you need ensure your code executes on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// continue with your program on the main thread, for example:
// populate the **mUser** with data
[self.tableView reloadData];
...
})
} else {
NSLog(#"Facebook error encountered: %#", error);
}
}];
} else {
NSLog(#"Facebook session is closed");
}
Related
I have to sync a bunch of information from my RestAPI. I must do 6 RestAPI calls to complete work. I designed API calls with Blocks, and return NSError if there is any.
3 of these calls should to execute nested because the first call gives information to others and allows the execution while the other 3 calls can run independently.
Due to improve network performance, I designed my synchronization call as following:
1 NSBlockOperation that contains the first nested 3 blocks;
1 NSBlockOperation that contains other three blocks;
1 NSBlockOperation that I use as "semphore" and tells me when all work done.
Last NSBlockOperation has dependency to previous two NSBlockOperation.
I also have a NSOperationQueue that contains all three NSBlockOperation where the semaphore NSBlockOperation is added as last in the queue.
The result that I would to achieve is: first two blocks called Concurrently and when their work finish, the semaphore NSBlockOperation is called and returns controls to User providing UIAlertMessage.
The result isn't that previously explained: controls are returned without waiting the end of syncAllBlocksInformation block.
Below the code that contains NSBlockOperation:
-(void)syncAllBlocksInformation:(void(^)(NSError *error))completion{
__block NSError *blockError = nil;
NSOperation *syncUserInfoOperation = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncUserInfo:tfMail.text password:tfPassword.text completion:^(NSError *error, NSNumber *idUser) {
if(!error){
[dataSync syncUserfilesInfo:idUser completion:^(NSError *error) {
if(!error){
[dataSync syncUserBookings:^(NSError *error) {
if(error){
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}];
NSBlockOperation *otherSyncOperations = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncNewsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncLocationsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncExoticAnimalTypesAndAnimals:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"END");
}];
[completionOperation setCompletionBlock:^{
NSLog(#"Syc isEx %i",syncUserInfoOperation.isExecuting);
NSLog(#"other isEx %i",otherSyncOperations.isExecuting);
completion(blockError);
}];
NSOperationQueue *opQueue = [NSOperationQueue new];
[completionOperation addDependency:syncUserInfoOperation];
[completionOperation addDependency:otherSyncOperations];
[opQueue addOperation:syncUserInfoOperation];
[opQueue addOperation:otherSyncOperations];
[opQueue addOperation:completionOperation];
}
And here, code that calls above block:
-(IBAction)login:(id)sender{
[self dismissKeyboardOpened:nil];
hud=[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[hud setLabelText:NSLocalizedString(#"login_hud_message", login_hud_message )];
[hud setMode:MBProgressHUDModeIndeterminate];
[self showHudAndNetworkActivity:YES];
[self syncAllBlocksInformation:^(NSError *error) {
[self showHudAndNetworkActivity:NO];
if(!error){
NSLog(#"End LOGIN");
[self showAlert:#"Login" message:#"Login OK" dismiss:YES];
}
else{
[self showAlert:#"Error" message:#"Login NO" dismiss:NO];
}
}];
}
What's wrong ?
The issue is that NSBlockOperation is for synchronous blocks. It will be finished as soon as its block(s) have finished executing. If its block(s) fire off asynchronous methods, those will run independently.
For example, when your syncUserInfoOperation's block is executed, it fires off [dataSync syncUserInfo:...] and then considers itself done; it doesn't wait for any of the completion handlers to fire, or anything like that.
A good solution to this is to create your own NSOperation subclasses. You'd probably want to create one for each of your data sync types to make it easier to setup dependencies, etc., but that's up to you. You can read all about how to do that here (be sure to read the section on "Configuring Operations for Concurrent Execution").
You could also make a generic NSOperation subclass that takes a block that can be run asynchronously. The main issue with that is it makes it much harder to handle things like canceling the operation, which you probably still want.
I have a unit test in which I need to wait for an async task to finish. I am trying to use NSConditionLock as it seems to be a pretty clean solution but I cannot get it to work.
Some test code:
- (void)testSuccess
{
loginLock = [[NSConditionLock alloc] init];
Login login = [[Login alloc] init];
login.delegate = self;
// The login method will make an async call.
// I have setup myself as the delegate.
// I would like to wait to the delegate method to get called
// before my test finishes
[login login];
// try to lock to wait for delegate to get called
[loginLock lockWhenCondition:1];
// At this point I can do some verification
NSLog(#"Done running login test");
}
// delegate method that gets called after login success
- (void) loginSuccess {
NSLog(#"login success");
// Cool the delegate was called this should let the test continue
[loginLock unlockWithCondition:1];
}
I was trying to follow the solution here:
How to unit test asynchronous APIs?
My delegate never gets called if I lock. If I take out the lock code and put in a simple timer it works fine.
Am I locking the entire thread and not letting the login code run and actually make the async call?
I also tried this to put the login call on a different thread so it does not get locked.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[login login];
});
What am I doing wrong?
EDIT adding login code. Trimmed do the code for readability sake. Basically just use AFNetworking to execute a POST. When done will call delegate methods.
Login make a http request:
NSString *url = [NSString stringWithFormat:#"%#/%#", [_baseURL absoluteString], #"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (_delegate) {
[_delegate loginSuccess];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (_delegate) {
[_delegate loginFailure];
}
}];
The answer can be found in https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPRequestOperation.m.
Since you are not setting the completionQueue property of the implicitly created AFHTTPRequestOperation, it is scheduling the callbacks on the main queue, which you are blocking.
Unfortunately, many answers (not all) in the given SO thread ("How to unit test asynchronous APIs?") are bogus and contain subtle issues. Most authors don't care about thread-safity, the need for memory-barriers when accessing shared variables, and how run loops do work actually. In effect, this leads to unreliable and ineffective code.
In your example, the culprit is likely, that your delegate methods are dispatched on the main thread. Since you are waiting on the condition lock on the main thread as well, this leads to a dead lock. One thing, the most accepted answer that suggests this solution does not mention at all.
A possible solution:
First, change your login method so that it has a proper completion handler parameter, which a call-site can set in order to figure that the login process is complete:
typedef void (^void)(completion_t)(id result, NSError* error);
- (void) loginWithCompletion:(completion_t)completion;
After your Edit:
You could implement your login method as follows:
- (void) loginWithCompletion:(completion_t)completion
{
NSString *url = [NSString stringWithFormat:#"%#/%#", [_baseURL absoluteString], #"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completion) {
completion(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) {
completion(nil, error);
}
}];
Possible usage:
[self loginWithCompletion:^(id result, NSError* error){
if (error) {
[_delegate loginFailure:error];
}
else {
// Login succeeded with "result"
[_delegate loginSuccess];
}
}];
Now, you have an actual method which you can test. Not actually sure WHAT you are trying to test, but for example:
-(void) testLoginController {
// setup Network MOCK and/or loginController so that it fails:
...
[loginController loginWithCompletion:^(id result, NSError*error){
XCTAssertNotNil(error, #"");
XCTAssert(...);
<signal completion>
}];
<wait on the run loop until completion>
// Test possible side effects:
XCTAssert(loginController.isLoggedIn == NO, #""):
}
For any other further steps, this may help:
If you don't mind to utilize a third party framework, you can then implement the <signal completion> and <wait on the run loop until completion> tasks and other things as described here in this answer: Unit testing Parse framework iOS
I've integrated with Facebook so that I can, among other things, post statuses to my feed. I based some of my code off of the publish to feed developer tutorial. When running the following Graph API request from my iOS application the completion block of the request is never called and no error appears in the XCode debug log.
[FBRequestConnection
startWithGraphPath:#"me/feed"
parameters:params
HTTPMethod:#"POST"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
DLog(#"error: domain = %#, code = %d", error.domain, error.code);
} else {
DLog(#"Posted action, id: %#", result[#"id"]);
}
}];
I have two convenience functions that perform checks against the current activeSession before executing this request. They look like this:
+ (BOOL)facebookSessionIsOpen {
return (FBSession.activeSession.isOpen);
}
+ (BOOL)facebookSessionHasPublishPermissions {
if ([FBSession.activeSession.permissions indexOfObject:#"publish_actions"] == NSNotFound ||
[FBSession.activeSession.permissions indexOfObject:#"publish_stream"] == NSNotFound ||
[FBSession.activeSession.permissions indexOfObject:#"manage_friendlists"] == NSNotFound) {
return NO;
} else {
return YES;
}
}
Both of these functions return YES indicating an active session with the necessary publishing permission. What's more confusing is that I can pull the user's profile without issue after performing the same checks successfully (granted publishing permissions are not required to pull the profile) using the following code:
[FBRequestConnection
startWithGraphPath:#"me"
parameters:[NSDictionary dictionaryWithObject:#"picture,id,birthday,email,location,hometown" forKey:#"fields"]
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
NSDictionary* resultDict = (NSDictionary*)result;
NSString* emailAddress = resultDict[#"email"];
NSString* location = resultDict[#"location"][#"name"];
NSString* birthday = resultDict[#"birthday"];
NSString* homeTown = resultDict[#"hometown"][#"name"];
...
}];
Any suggestions on how to debug this issue?
Turns out the issue was a threading one. The Facebook iOS SDK doesn't seem to like to execute a FBRequest on a different thread from the one that you called openActiveSessionWithReadPermissions on and promptly deadlocks. It turns out I was running the postStatus request in a separate thread like so:
dispatch_queue_t some_queue = dispatch_queue_create("some.queue.name", NULL);
dispatch_async(some_queue, ^{
[FacebookHelper postStatusToFacebookUserWall:newStatus withImage:imageData];
}];
Make sure your openActiveSessionWithReadPermissions and any FBRequest permutations all happen on the same thread, or you'll likely run into these silent failures.
I created a model called User that have a property "name".
I'm making a request to facebook API (using the latest iOS SDK), the idea is set my user.name property when the facebook return the data.
The facebook return the data into the startWithCompletionHandler block, but I can't set this data to my user object, I only can access the data on startWithCompletionHandler. When I try access the data out of the block my model returns NULL.
How can I fill my model/object when facebook request returns?
My current method implementation:
+ (void)requestUserData:(User *)userModel {
if(FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
if(user.name) [self setUserData:user userModel:userModel];
}];
}
}
+ (void)setUserData:(NSDictionary<FBGraphUser> *)user userModel:(User *)userModel {
userModel.name = user.name;
}
And the call:
__block User *user = [[User alloc] init];
[GFFacebookHelpers requestUserData:user];
NSLog(#"user: %#", user.name); //this part prints 2013-06-06 18:03:43.731 FacebookClassTest[74172:c07] user: (null)
Thanks.
The Facebook's SDK is fetching asynchronously, so the completion handler you've written is executed after your helper method returns.
So when your method returns the User object of yours it is, logically just an empty user (since you alloc/init'ed it already).
When the completion block is invoked, that user object is updated but by this time, you're not processing it anymore in your app (view controller).
So here is my suggestion: Either use the Facebook SDK more directly from your view controllers, or if you want to keep all this logic in your helper class, then change it so your method doesn't return data, but it requires a block which will be invoked when the data request has finished, something more like this:
+ (void)requestUserDataWithCompletionHandler:(void (^)(User *user, NSError *error))handler{
if(FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
if(!error){
User *userData = [[User alloc] init];
userData.name = user.name;
dispatch_async(dispatch_get_main_queue(), ^{
handler(userData, nil);
});
}
else{
dispatch_async(dispatch_get_main_queue(), ^{
handler(nil, error);
});
}
}];
}
}
Note I've wrapped the invocation of the handler block in a dispatch_async() to the main queue to make sure you're good to go to update any UI.
Your view controller's method will now look like this:
- (void)updateStatusLabels {
if([GFFacebookHelpers isLogged]){
[GFFacebookHelpers requestUserDataWithCompletionHandler:^(User *user, NSError *error) {
if(!error){
self.fbStatus.text = user.name;
[_loginButton setTitle:#"Logout" forState:UIControlStateNormal];
}
}];
}else{
self.fbStatus.text = #"You need to login";
[_loginButton setTitle:#"Login" forState:UIControlStateNormal];
}
}
At first
I have this
ZTCAPIClient *api = [ZTCAPIClient sharedClient];
__block BOOL sessionSuccess = NO;
//Get session
[api getPath:#"api-getsessionid.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSMutableDictionary *dict = [self dealWithZTStrangeJSON:JSON];
if ([dict count]) {
NSLog(..something..);
sessionSuccess = YES;
NSLog(#"inside:%u",sessionSuccess);
} else {
NSLog(#"ERROR: Get no session!");
sessionSuccess = NO;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error);
sessionSuccess = NO;
}];
[api.operationQueue waitUntilAllOperationsAreFinished];
NSLog(#"outside:%u",sessionSuccess);
but I will get:
outside:0
inside:1
I know it's the async reason.
So I searched on the Internet, then I found this: wait until multiple operations executed - including completion block (AFNetworking)
So I try it:
ZTCAPIClient *api = [ZTCAPIClient sharedClient];
__block BOOL sessionSuccess = NO;
dispatch_group_t group = dispatch_group_create();
//Get session
dispatch_group_enter(group);
[api getPath:#"api-getsessionid.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSMutableDictionary *dict = [self dealWithZTStrangeJSON:JSON];
if ([dict count]) {
NSLog(..something..);
sessionSuccess = YES;
NSLog(#"inside:%u",sessionSuccess);
} else {
NSLog(#"ERROR: Get no session!");
sessionSuccess = NO;
}
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error);
sessionSuccess = NO;
dispatch_group_leave(group);
}];
//[api.operationQueue waitUntilAllOperationsAreFinished];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
DLog(#"outside:%u",sessionSuccess);
then I get nothing...
nothing output.
Where is wrong?
You're probably not getting any output because your program is never moving past the call to dispatch_group_wait. If it did, then you'd see the "outside" log statement.
If dispatch_group_wait is never returning, then there must still be something in the group. In your sample code, you add one thing to the group with dispatch_group_enter and then remove it in either the success or failure handler for the api call with dispatch_group_leave. This means that dispatch_group_leave is not being called for some reason.
My suspicion is that the reason the blocks are not being called is that they will be invoked asynchronously on the same dispatch queue that your outer code runs on. If this is the case, then they can't run until dispatch_group_wait returns and dispatch_group_wait cannot return until the blocks run. This is called deadlock. (Edit: Alternatively, it could be that some part of the program that invokes the success or failure blocks is the part that is leading to deadlock. Either way, the result is that the blocks can't get called since dispatch_group_wait never returns.)
The other possibility is that the method -dealWithZTStrangeJSON: never returns for some reason. If this is the case, then the success block will be invoked (you could set a breakpoint on its first line to verify), but it will never make it to dispatch_group_leave.
In either case, I would recommend that you think about solving your problem another way instead of waiting for the operation to finish. Perhaps you can do the things that you were planning to do after dispatch_group_wait returns inside of the success handler instead (or another way of thinking about it would be that the success or failure handler could call a method that does the things you're currently doing after dispatch_group_wait—either way will work, but sometimes I find that it's easier to keep my code organized by calling out to a method instead of putting all of the code in a block. This could be especially useful if you want to share some code between the success and failure blocks).