I am using a block from Facebook SDK. It returns a dictionary. I want that dictionary as a return value of a method. I am trying to wrap my head around the whole block concept but need a nudge in the right direction.
The block:
(the argument for the block is a string userFBid)
-(NSDictionary*) getMutualFBFriendsWithFBid:(NSString*)fbID {
[FBRequestConnection startWithGraphPath:[NSString stringWithFormat:#"/%#/mutualfriends/%#", [[PFUser currentUser] objectForKey:kFbID],userFBid]
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
) {
result = (NSDictionary*)result;
//return result;
}];
}
How do i get the return value?
I have tried to google it, but i cant get my hand around it.
I would appreciate any pointer in the right direction.
EDIT:
The main question is the following: I need to the completion handler to call a method in another class... how to do that?
As the method startWithGraphPath is asynchronous, you can't code as if it was synchronous : it means no return value, because as soon as this method is called, your app will continue to execute to the next line, and won't wait for a returned value.
So, to keep this async, I assume you want to use the result of this in your own function, so call it in your completionHandler block:
[FBRequestConnection startWithGraphPath:[NSString stringWithFormat:#"/%#/mutualfriends/%#", [[PFUser currentUser] objectForKey:kFbID],userFBid]
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
[self myRockinFunction:result];
}];
//Considering this function
-(void)myRockinFunction:(NSDictionary*) fb_result{
//Do stuff with fb_result
}
Edit
OK, I get it. Modify your method to accept a callback as a parameter :
-(NSDictionary*) getMutualFBFriendsWithFBid:(NSString*)fbID andCallback:(void (^)(NSDictionary *))callback {
[FBRequestConnection startWithGraphPath:[NSString stringWithFormat:#"/%#/mutualfriends/%#", [[PFUser currentUser] objectForKey:kFbID],userFBid]
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection,id result,NSError *error) {
//You should treat errors first
//Then cast the result to an NSDictionary
callback((NSDictionary*) result); //And trigger the callback with the result
}];
}
Then, in your other class, use another block to treat your result :
[YourHelperClass getMutualFBFriendsWithFBid:fbID andCallback:^(NSDictionary* result){
//Use result how you wish
//Beware, this is async too.
}];
Note : you should treat the error before triggering your callback.
Edit 2 (Help from other users appreciated)
Even better, you might try to pass a callback taking all the parameters (not tested, and not sure of the syntax. If someone can correct me, I'd appreciate):
-(NSDictionary*) getMutualFBFriendsWithFBid:(NSString*)fbID andCallback:(void (^)(FBRequestConnection *,NSDictionary *,NSError *))callback {
[FBRequestConnection startWithGraphPath:[NSString stringWithFormat:#"/%#/mutualfriends/%#", [[PFUser currentUser] objectForKey:kFbID],userFBid]
parameters:nil
HTTPMethod:#"GET"
completionHandler:callback()]; //Not sure here!
}
[YourHelperClass getMutualFBFriendsWithFBid:fbID andCallback:^(FBRequestConnection *connection,NSDictionary * result,NSError *error){
//You could deal with errors here now
}];
Here's a reference on Apple's docs for deeper understanding.
You already have it :)
I would write a method to process the dictionary, in order to keep the completionHandler block a little cleaner--but you could write your response-handling code inside the block. As another commenter mentioned, this is async so you're not really "returning" anything...you're handling the completion block when it gets called.
To help you understand a little, the completionHandler block in this case is a chunk of code that you're passing to the method as an argument, for it to call later. In essence, "whenever this call comes back, do this: ^{ ". The implementation of the FBRequest method will call your completionHandler (whatever that may be).
Related
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");
}
Problem-> i just wanna fetch scores of friends not on main queue but on different queue and in that queue I also called login, openSession methods for sign in. I wanna try to fetch scores
STEP 1 -> -(void)requestcallerWithQueue in this method i m trying to fetch score inside queue created by me (not main queue) then FBRequestConnection startWithGraphPath: method call in FBRequestConnection startWithGraphPath: NOT RESPONDING.
STEP 2 -> -(void)requestcallerWithoutQueue in this method i m trying to fetch score inside queue created by me (not main queue) then FBRequestConnection startWithGraphPath: method call in FBRequestConnection startWithGraphPath: RESPONDING
**CODE IS BELOW FOR REFERENCE**
-(void)fetchscoreWithCallback:(void (^)(NSDictionary*))callback
{
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:#"%ld", (long)10], #"score",
nil];
// [NSString stringWithFormat:#"%llu/scores", lintFBFriendId]
[FBRequestConnection startWithGraphPath:#"245526345633359/scores"
parameters:params
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
NSLog(#"result->%#",result);
}
}
NOT WORKING
-(void)requestcallerWithQueue
{
dispatch_queue_t some_queue = dispatch_queue_create("some.queue.name", NULL);
dispatch_async(some_queue,
^{
[self fetchscoreWithCallback:callback];
}];
}
WORKING
-(void)requestcallerWithoutQueue
{
[self fetchscoreWithCallback:callback];
}
FBRequestConnection uses NSURLConnection which by default requires the calling thread to have a run loop. For non main threads, you have to manage the run loop yourself or dispatch the work back to the main queue. For some resources, check out:
http://coc24hours.blogspot.com/2012/01/using-nsrunloop-and-nsurlconnection.html
NSURLConnection needs a NSRunLoop to execute?
http://iosdevelopmentjournal.com/blog/2013/01/27/running-network-requests-in-the-background/
I am new to iOS programming and I just want to know that how can we reply on a specific post?
I have read a lot of links but I can't get the point.
http://developers.facebook.com/docs/graph-api/reference/object/comments
I have use this method but it is not working properly this code update/edit your post or comment:
-(void) abc
{
[FBRequestConnection startWithGraphPath:#"/YOUR_POST_ID/comments"
parameters:nil
HTTPMethod:#"GET"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
) {
/* handle the result */
NSLog(#"Results == %#",result);
NSLog(#"error == %#",error);
}];
}
and plus I am calling this method into my textfield
[myReplyingUITextField addTarget:self action:#selector(abc) forControlEvents:(UIControlEventEditingDidEndOnExit)];
You need to add the message parameter to your API call. Pass the comment into this parameter and you'll be able to comment on the post. See the API documentation.
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 have multiple methods, each with nested loops and facebook requests. There is an array of X id's and each method loops through each id, makes a request for that id then does stuff with the result data.
I need to be notified when each method has completed... ie, when the method has finished looping through all the id's in the array, making the facebook request for each, received the results and finished its tasks with the resulting data. I can't seem to figure out how to make this happen. here are examples of the methods:
- (void)runLoopForFacebookFriendsContent1 {
for (NSString *fbIdStr in self.fbIdsArr){
FBRequest *fbRequest = [FBRequest requestWithGraphPath:graphPathString parameters:nil HTTPMethod:#"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
//show alert
} else {
//Do stuff with the resulting data
}
}];
}
}
- (void)runLoopForFacebookFriendsContent2 {
for (NSString *fbIdStr in self.fbIdsArr){
FBRequest *fbRequest2 = [FBRequest requestWithGraphPath:graphPathStringNumber2 parameters:nil HTTPMethod:#"GET"];
[fbRequest2 startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
//show alert
} else {
for (PF_FBGraphObject *obj in [result objectForKey:#"data"]){
NSLog(#"facebook result: %#",result);
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setValue:#"type2" forKey:#"ContentType"];
[dict setValue:obj forKey:#"data"];
[self.theFacebookDataArray addObject:dict];
}
}
}];
}
}
I call these methods in viewWillAppear. is there a way to setup some sort of completion handler to put the call for these methods inside? and then post an NSNotification when they are all done?
One option is to take a look at the ReactiveCocoa framework. I find it helps tremendously with this kind of pattern, after you get past a bit of a learning curve.
The following is an example directly from the linked file:
// Perform 2 network operations and log a message to the console when they are
// both completed.
//
// +merge: takes an array of signals and returns a new RACSignal that passes
// through the values of all of the signals and completes when all of the
// signals complete.
//
// -subscribeCompleted: will execute the block when the signal completes.
[[RACSignal
merge:#[ [client fetchUserRepos], [client fetchOrgRepos] ]]
subscribeCompleted:^{
NSLog(#"They're both done!");
}];
You could adapt this to the Facebook SDK fairly easily.
This behavior is expected since you are calling dispatch_group_enter and then instantly calling dispatch_group_leave which means the group has nothing to wait for.
You should call dispatch_group_enter before every block and call dispatch_group_leave at the end of every block.
Check the accepted answer here:
Wait until multiple networking requests have all executed - including their completion blocks
Update:
For the given example, you can call dispatch_group_enter before every call to startWithCompletionHandler:, and call dispatch_group_leave at the end of the completion block:
for (...) {
FBRequest *fbRequest = ...;
dispatch_group_enter(group);
[fbRequest startWithCompletionHandler:^(...) {
...
dispatch_group_leave(group);
}
}
Didn't want to have to answer my own question... don't really like doing that, and was hoping for a more elegant solution... but it doesnt seem to be coming and someone even downvoted the question. So i'm just going to post my solution and close the question out. If you end up with a similar problem and are reading this, I dealt with it by:
creating 2 int's for each method. One int is immediately set with the count of the array being iterated through. at the end of each iteration, I am posting an NSNotification. In the NSNotification handler method, i am ++ incrementing the second int & each time running an if condition, checking to see if they match.
It's a pain to keep track of all these when there are many methods like this happening... So if anyone ever finds a better solution, I'd love to hear it. Thanks to everyone who answered and tried to be helpful, I appreciate it!