AFNetworking block variable update issue - iphone

I am using AFNetworking to receive JSON back from the server, and I use this JSON to determine the return value for my Objective-C function. Regardless of what the JSON is though, the value of d doesn't change from when it is initialized (as a false value). Why is this happening? My code is as follows:
-(BOOL)someFunction{
__block BOOL d;
d = FALSE;
//the value of d is no longer changed
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *response, id jsonObject) {
if(![[jsonObject allKeys] containsObject:#"someString"]){
d = TRUE;
}
else {
d = FALSE;
}
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *response, NSError *error, id jsonObject) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Alert"
message: #"Could not connect to server!"
delegate: nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
d = FALSE;
}];
[operation start];
return d;
}

You should use an async function with a completion block that returns a success BOOL.
- (void)myBeautifulFunction
{
[self someFunctionWithCompletion:^(BOOL success) {
if (!success) {
[self showAlert];
}
}];
}
- (void)someFunctionWithCompletion:(void (^)(BOOL success))completion
{
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *response, id jsonObject) {
if (![[jsonObject allKeys] containsObject:#"someString"]){
if (completion) {
completion(YES);
}
} else {
if (completion) {
completion(NO);
}
}
} failure:^(NSURLRequest *req, NSHTTPURLResponse *response, NSError *error, id jsonObject) {
if (completion) {
completion(NO);
}
}];
[operation start];
}

The operation will be performed asynchronously but you return the value of d synchronously. You should trigger whatever requires the value of d from the operations completion block

Related

How to wait then perform an action based on HTTPrequest response iOS

I have class that post a tweet to twitter using HTTP Post
here is a bit of code
PostTweet.h
#interface PostTweet : NSObject
- (void)postMyTweet;
#end
PostTweet.m
- (void)postMyTweet
{
accountStore = [[ACAccountStore alloc] init];
accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
if (granted)
{
allAccounts = [accountStore accountsWithAccountType:accountType];
if ([allAccounts count] > 0)
{
userAccount = [allAccounts objectAtIndex:0];
userName = userAccount.username;
NSURL * reqURL = [NSURL URLWithString:ENDPOINT_MEDIA_UPLOAD];
NSDictionary * parameter = [NSDictionary dictionaryWithObject:tweetTitle forKey:#"status"];
SLRequest *twitterInfoRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST
URL:reqURL
parameters:parameter];
[twitterInfoRequest addMultipartData:tweetImage withName:PARAM_MEDIA type:CONTENT_TYPE_MULTIPART_FORM_DATA filename:nil];
[twitterInfoRequest setAccount:userAccount];
[twitterInfoRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
//show status after done
long result = [urlResponse statusCode];
//Let us say that every thing is ok and I got 200 response
if (result == 200)
{
NSLog(#"%ld",result);
}
}
];
}
}
else
{
NSLog(#"Not authorized");
}
}];
}
In my viewcontroller.m
- (void) actuallySendTweet
{
PostTweet * pt = [[PostTweet alloc] init];
[pt postTweet];
NSLog(#"Done");
}
The Question is: after calling The testMethod, How to wait for the http request response and I can do anything based on the response.
What happens now is that as soon as I call the testMethod the NSLog perform right away and does not wait for the http response.
First, if you wanted to coordinate two different threads dispatch_semaphore_t might be more appropriate than dispatch_group_t.
Second, and more importantly, you should not take an asynchronous method such as performRequestWithHandler, invoke it from the main queue in a synchronous manner. You never should be blocking the main queue.
Fortunately performRequestWithHandler gives us a handler block which we can use to perform actions after the tweet is done. In your comments, you say you simply want to update your HUD after the tweet, so you should do that performRequestWithHandler (dispatching that UI update back to the main queue, because, as the documentation says, "handler is not guaranteed to be called on any particular thread"):
- (void)postMyTweet
{
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
if (granted)
{
NSArray *allAccounts = [accountStore accountsWithAccountType:accountType];
if ([allAccounts count] > 0)
{
ACAccount *userAccount = [allAccounts objectAtIndex:0];
NSURL *reqURL = [NSURL URLWithString:ENDPOINT_MEDIA_UPLOAD];
NSDictionary *parameter = [NSDictionary dictionaryWithObject:tweetTitle forKey:#"status"];
SLRequest *twitterRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST
URL:reqURL
parameters:parameter];
[twitterRequest addMultipartData:tweetImage withName:PARAM_MEDIA type:CONTENT_TYPE_MULTIPART_FORM_DATA filename:nil];
[twitterRequest setAccount:userAccount];
[twitterRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (error)
NSLog(#"tweet fail; error = %#", error);
else
{
long result = [urlResponse statusCode];
if (result == 200)
NSLog(#"%ld",result);
else
NSLog(#"Unexpected response: %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]]);
}
// Dispatch UI updates back to main queue
dispatch_async(dispatch_get_main_queue(), ^{
// do your MBProgressHUD stuff here
});
}];
}
}
else
{
NSLog(#"Not authorized");
}
}];
}
You also asked "How can I pass the HTTP response result to the viewcontroller?" You obviously do all of this in performRequestWithHandler, where you have the HTTP response (and the response data).
If you want postTweet to operate synchronously, then best practices would dictate that you don't submit it from the main queue (because, at the risk of sounding like a broken record, you never want to block the main queue). But you could have actuallySendTweet dispatch this tweet from a background queue, e.g.:
- (void) actuallySendTweet
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PostTweet * pt = [[PostTweet alloc] init];
[pt postTweetSynchronously];
NSLog(#"Done");
dispatch_async(dispatch_get_main_queue(), ^{
// Now do any UI updates you want here.
// For example, do your MBProgressHUD update here.
});
});
}
- (void)postTweetSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
if (granted)
{
NSArray *allAccounts = [accountStore accountsWithAccountType:accountType];
if ([allAccounts count] > 0)
{
ACAccount *userAccount = [allAccounts objectAtIndex:0];
NSURL *reqURL = [NSURL URLWithString:ENDPOINT_MEDIA_UPLOAD];
NSDictionary *parameter = [NSDictionary dictionaryWithObject:tweetTitle forKey:#"status"];
SLRequest *twitterRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST
URL:reqURL
parameters:parameter];
[twitterRequest addMultipartData:tweetImage withName:PARAM_MEDIA type:CONTENT_TYPE_MULTIPART_FORM_DATA filename:nil];
[twitterRequest setAccount:userAccount];
[twitterRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
// do whatever you want here, perhaps updating some class properties
// now that we're done, signal the semaphore
dispatch_semaphore_signal(semaphore);
}];
}
}
else
{
NSLog(#"Not authorized");
dispatch_semaphore_signal(semaphore); // make sure to signal here, too
}
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
As here you are using completion block. The thread does not wait for the execution of block.
So it you want that execution of block should complete and precess the data before finishing the execution of method, you can use,
dispatch_group_t
I am editing your method for that,
- (void)postMyTweet
{
accountStore = [[ACAccountStore alloc] init];
accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
if (granted)
{
allAccounts = [accountStore accountsWithAccountType:accountType];
if ([allAccounts count] > 0)
{
userAccount = [allAccounts objectAtIndex:0];
userName = userAccount.username;
NSURL * reqURL = [NSURL URLWithString:ENDPOINT_MEDIA_UPLOAD];
NSDictionary * parameter = [NSDictionary dictionaryWithObject:tweetTitle forKey:#"status"];
SLRequest *twitterInfoRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST
URL:reqURL
parameters:parameter];
[twitterInfoRequest addMultipartData:tweetImage withName:PARAM_MEDIA type:CONTENT_TYPE_MULTIPART_FORM_DATA filename:nil];
[twitterInfoRequest setAccount:userAccount];
[twitterInfoRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
//show status after done
long result = [urlResponse statusCode];
//Let us say that every thing is ok and I got 200 response
if (result == 200)
{
NSLog(#"%ld",result);
}
dispatch_group_leave(group);
}
];
}
}
else
{
NSLog(#"Not authorized");
dispatch_group_leave(group);
}
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
}
Now from this you can get idea.

Facebook sdk 3 returns error code 5

I'm using the new facebook SDK... Here I want to post a image from my application to app.
This is the code
NSData* imageData = UIImagePNGRepresentation(emailImage);
NSLog(#"%#",emailImage);
if (xapp.session.isOpen) {
[xapp.session closeAndClearTokenInformation];
} else {
if (xapp.session.state != FBSessionStateCreated) {
// Create a new, logged out session.
xapp.session = [[FBSession alloc] init];
}
[xapp.session openWithCompletionHandler:^(FBSession *session,
FBSessionState status,
NSError *error) {
}];
}
self.postParams =[[NSMutableDictionary alloc] initWithObjectsAndKeys:imageData, #"source",#"My Art", #"message",nil];
// No permissions found in session, ask for it
if ([[FBSession activeSession]isOpen]) {
if ([[[FBSession activeSession]permissions]indexOfObject:#"publish_actions"] == NSNotFound) {
[[FBSession activeSession] reauthorizeWithPublishPermissions:[NSArray arrayWithObjects:#"publish_actions",#"publish_stream",nil]
defaultAudience:FBSessionDefaultAudienceOnlyMe
completionHandler:^(FBSession *session, NSError *error) {
[self publishStory];
}];
}else{
[self publishStory];
}
}else{
NSLog(#"Sessionisclosed");
/
[FBSession openActiveSessionWithPublishPermissions:[NSArray arrayWithObjects:#"publish_stream",#"publish_actions",nil]
defaultAudience:FBSessionDefaultAudienceOnlyMe
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
if (!error && status == FBSessionStateOpen) {
[FBSession setActiveSession:session];
[self publishStory];
}else{
NSLog(#"error:%d",[error code]);
}
}];
}
- (void)publishStory
{
NSLog(#"PublishStory%#",self.postParams);
[FBRequestConnection
startWithGraphPath:#"me/feed"
parameters:self.postParams
HTTPMethod:#"POST"
completionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
NSString *alertText;
if (error) {
alertText = [NSString stringWithFormat:
#"error: domain = %#, code = %d",
error.domain, error.code];
} else {
alertText = [NSString stringWithFormat:
#"Posted action, id: %#",
[result objectForKey:#"id"]];
}
// Show the result in an alert
[[[UIAlertView alloc] initWithTitle:#"Result"
message:alertText
delegate:nil
cancelButtonTitle:#"OK!"
otherButtonTitles:nil]
show];
}];
NSLog(#"PublishStory");
}
Here I'm getting error code 5,which says that there is some permissions problems while posting the image ... But I have used both publish_stream and publish_actions ..I don't know what I'm missing ......
You need to switch to feed dialog because the opengraph is deprecated since feb 2013
refer the link for feeddialog
http://developers.facebook.com/docs/reference/dialogs/feed/

Calling a GCD Block else where in Main Thread

I am using following Code to get response from a string query. There are a lot of queries all around in my app and i want to copy and paste this code again and again
Is there any way I can just make an instance of it , pass the urlString and then return the response..
I have tried creating a function
+(NSString*) myFunc{} in an NSObject Class but it seems that GCD Doesn't work except Main UI Threads. How can i fix this issue
__block__ NSString *response;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//your server url and request. data comes back in this background thread
response; = [NSString stringWithContentsOfURL:[NSURL URLWithString:queryString] encoding:NSUTF8StringEncoding error:&err];
dispatch_async(dispatch_get_main_queue(), ^{
//update main thread here.
NSLog(#"%#",response); // NEED TO RETURN THIS
if (err != nil)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle: #"Error"
message: #"An error has occurred."
delegate: self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[indicator stopAnimating];
}
});
});
I would separate out the request processing from the error reporting, using a completion block to provide the feedback to the caller.
First define the completion block semantics; we know we want the string response and an optional error descriptor:
typedef void (^COMPLETION_BLOCK)(NSString *response, NSString *error);
Second implement the method that will get the response in the background and then call the completion block in the main thread. This could be a class method in some global utility class if you wish:
- (void)responseFromURL:(NSURL *)url
completionBlock:(COMPLETION_BLOCK)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
NSString *response = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(response, error);
}
}
}
And finally call the method:
[someClass responseFromURL:[NSURL URLWithString:queryString]
completionBlock:^(NSString *response, NSError *error) {
NSLog(#"response='%#'", response);
if (error != nil)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error getting response"
message:[error localizedDescription]
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[indicator stopAnimating];
}
}];
(this code is untested and will therefore doubtless contain some bugs)

issue with NSOperationQueue, weakSelf and blocks

I have the following code:
[[AHPinterestAPIClient sharedClient] getPath:requestURLPath parameters:nil
success:^(AFHTTPRequestOperation *operation, id response) {
[weakSelf.backgroundQueue_ addOperationWithBlock:^{
[self doSomeHeavyComputation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf.collectionView_ setContentOffset:CGPointMake(0, 0)];
[weakSelf.collectionView_ reloadData];
[weakSelf.progressHUD_ hide:YES];
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:#"UIScrollViewDidStopScrolling" afterDelay:0.3];
[weakSelf.progressHUD_ hide:YES];
}];
}];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[weakSelf.progressHUD_ hide:YES];
[weakSelf.collectionView_.pullToRefreshView stopAnimating];
NSLog(#"Error fetching user data!");
NSLog(#"%#", error);
}];
For some reason this worked just fine in iOS 5, but not iOS 6 (it crashes). Now I am not going to ask about iOS 6 because it's still under NDA. What I want to know is, whether the code above is wrong? If yes how do I fix it.
If I put the code inside the block outside of the mainQueue then it's fine.
What I am trying to do here is to do the NSOperationQueue mainQueue only after the [self doSomeHeavyComputation] is done. So this is a dependency, how should I add this dependency?
Update:
Here's the crash log if it helps:
It is recommended to “unwind” weak references in the block, so please try this:
__weak id weakSelf = self;
[client getPath:path parameters:nil success:^(id op, id response) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
__weak id internalWeakSelf = strongSelf;
[strongSelf.backgroundQueue_ addOperationWithBlock:^{
id internalStrongSelf = internalWeakSelf;
if (internalStrongSelf == nil) return;
[internalStrongSelf doSomeHeavyComputation];
__weak id internalInternalWeakSelf = internalStrongSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
id internalInternalStrongSelf = internalInternalWeakSelf;
if (internalInternalStrongSelf == nil) return;
[internalInternalStrongSelf reloadCollectionView];
}];
}];
}
failure:^(id op, NSError *error) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
[strongSelf stopProgress];
NSLog(#"Error fetching user data: %#", error);
}];

IOS5 - AFNetworking processing request

I'm making an application in which I have to call some webservices. I chose to work with AFNetworking.
I followed the Twitter example provided in the library. Everything works well except that I have permanently the little "processing circle" in the notification bar (see the image below).
Here's the code I have for my request :
- (id)initWithAttributes:(NSDictionary *)attributes
{
self = [super init];
if (!self) {
return nil;
}
_name = [attributes valueForKeyPath:#"name"];
return self;
}
+ (void)itemsListWithBlock:(void (^)(NSArray *items))block
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *user = [defaults objectForKey:#"user"];
NSDictionary *company = [defaults objectForKey:#"company"];
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
/*
** [ Some stuff to set the parameters in a NSDictionnary ]
*/
MyAPIClient *client = [MyAPIClient sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSURLRequest *request = [client requestWithMethod:#"POST" path:#"getMyList" parameters:mutableParameters];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *mutableItems = [NSMutableArray arrayWithCapacity:[JSON count]];
for (NSDictionary *attributes in JSON) {
ListItem *item = [[ListItem alloc] initWithAttributes:attributes];
[mutableItems addObject:item];
}
if (block) {
block([NSArray arrayWithArray:mutableItems]);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
[[[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok", nil] show];
if (block) {
block(nil);
}
}];
[operation start];
}
Does this means my request isn't finished ? I'm not really getting what I'm doing wrong here...
If someone could help, I'd really appreciate. Thanks.
Don't call [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount]; this increase the activity count with 1 and the [operation start]; will call it also. now the activity count is 2 and will get decreased when the operation is done. But since you called the incrementActivityCount it will bring it back to 1 and not 0.
Just call [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; once, for example place it in the application:applicationdidFinishLaunchingWithOptions: method of your applications appDeletage.
Also I would suggest to add the operation to a NSOperationQueue and not just call start on it.