Dealing with AFNetworking2.0 asynchronous HTTP request - objective-c

I am very new to the concept of asynchronous programming, but I get the general gist of it (things get run in the backround).
The issue I'm having is I have a method which utilizes AFNetworking 2.0 to make an HTTP post request, and it works for the most part.
However, I can't figure out how to get the method to actually return the value received from the response as the method returns and THEN gets the value from the response.
-(int) registerUser
{
self.responseValue = 000; //Notice I set this to 000 at start of method
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSError *err = nil;
self.responseValue = [[responseObject objectForKey:#"res"] intValue];
//Note: This^ value returns 99 and NSLogs confirm this
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
}];
return self.responseValue; //This will return 000 and never 99!
}
Whats the 'proper' way to handle this situation? I've heard whispers of using a 'callback', but I don't really understand how to implement that in this situation.
Any guidance or help would be awesome, cheers!

The issue is that the POST runs asynchronously, as you point out, so you are hitting the return line well before the responseValue property is actually set, because that success block runs later. Put breakpoints/NSLog statements in there, and you'll see you're hitting the return line first.
You generally do not return values from an asynchronous methods, but rather you adopt the completion block pattern. For example:
- (void)registerUserWithCompletion:(void (^)(int responseValue, NSError *error))completion
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
int responseValue = [[responseObject objectForKey:#"res"] intValue];
if (completion)
completion(responseValue, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
if (completion)
completion(-1, err); // I don't know what you want to return if it failed, but handle it appropriately
}];
}
And then, you could use it as follows:
[self registerUserWithCompletion:^(int responseValue, NSError *error) {
if (error)
NSLog(#"%s: registerUserWithCompletion error: %#", __FUNCTION__, error);
else
NSLog(#"%d", responseValue);
// do whatever you want with that responseValue here, inside the block
}];
// Needless to say, don't try to use the `responseValue` here, because
// `registerUserWithCompletion` runs asynchronously, and you will probably
// hit this line of code well before the above block is executed. Anything
// that is dependent upon the registration must called from within the above
// completion block, not here after the block.
Note, I'd suggest you retire that responseValue property you had before, because now that you're using completion blocks, you get it passed back to you via that mechanism, rather than relying on class properties.

Check this one and use search ;-))
Getting variable from the inside of block
its a lot of duplicates already!
:-)

Related

Wait for AFNetworking completion block before continuing (i.e. Synchronous)

I have a use case for AFNetworking to behave synchronously (details below). How can I achieve this?
Here is my code snippet, which I've simplified as much as possible.
I would like to return the success response, but I only ever get nil (because the function returns before the block is called).
- (id)sendForUrl:(NSURL *)url {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
__block id response;
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
response = responseObject;
NSLog(#"JSON: %#", responseObject);
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return response;
}
Details
So the reason I need this to behave synchronously is because I am building a pod that will bootstrap an application at start up. The boot strapping hits a services and saves a bunch of values locally. These values are then used for the current session and not changed. If the values change, the user will get a strange experience, so its important I avoid this.
If the service is down, that's okay. We'll use default values or looks for some saved values from a previous session, but whatever happens, we don't want the experience for the user to change in the session.
(This is an engine for A/B testing and experimentation - if that helps you 'get' the use case).
Ignoring the fact that we generally do not want to make synchronous network requests, the traditional solution is to use semaphores:
- (id)sendForUrl:(NSURL *)url {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block id response;
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
response = responseObject;
NSLog(#"JSON: %#", responseObject);
dispatch_semaphore_signal(semaphore);
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return response;
}
There are two issues here:
I know you said you didn't want "to see some complex dispatch style things", but with due deference to others, a semaphore is better than a while loop that is spinning, polling until some value changes.
Note, assuming you're calling this method from the main thread, you must set the completionQueue to be some other, background queue. By default, the AFHTTPRequestOperationManager will use the main queue for those completion blocks. And if you block that thread waiting until the completion blocks to run on that same thread, you'll deadlock (i.e. your app will freeze).
For the sake of completeness, I'll point out that invariably, when someone asks "how to I make some asynchronous method behave synchronously", the correct answer is, "you don't". Instead, you generally adopt asynchronous patterns, for example changing the method to take a block parameter:
- (void)sendForUrl:(NSURL *)url completionHandler:(void (^)(id responseObject, NSError *error))completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
}
You'd then call it like so, providing a completion handler block:
[object sendForURL:url completionHandler:^(id responseObject, NSError *error) {
// use responseObject and/or error here
}];
// don't use them here
It sounds like there's a part of your app that can't run until a starting request is done. But there's also a part that can run (like the part that starts the request). Give that part a UI that tells the user that we're busy getting ready. No blocking the UI.
But if you must, and if AFNetworking doesn't provide a blocking version of a request (kudos to them for that), then you could always block the old fashioned way...
- (void)pleaseDontUseThisIdea {
__block BOOL thePopeIsCatholic = YES;
[manager GET: ....^{
// ...
thePopeIsCatholic = NO;
}];
while (thePopeIsCatholic) {}
}

How to return response object in AFNetworking in class method?

I’m sorry if this question is too basic, but I can’t seem to find a an answer online.
I want to fetch the JSON result and have them returned with the class method below. But as you can see, by fetching the JSON in the block method, I don’t have a way to return them as result.
What is the correct way to to return them as NSDictionary from inside block method, or is there any other way to simplify this?
+ (NSDictionary *) fetchtPostsCount:(NSString *) count
page: (NSString *) page {
NSDictionary *requestParameter = [[NSDictionary alloc] initWithObjectsAndKeys:count, #"count", page, #"page", nil];
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", error);
}];
return nil;
}
AFNetworking executes requests on different thread, and calls the success or failure block when its done. Conceptually, you can imagine that your fetchPostsCount method will have already completed and returned its value by the time request is finished.
You almost certainly want it to work that way. Running the request on another thread and NOT waiting for it, allows your main UI thread to continue processing events and rendering screen updates. You don't want to get in the way of those things, or the user (and iOS) will get unhappy.
However, if you insist on waiting for the request to complete before returning, you could set a flag to monitor the status of the request, and then wait on that flag until the request is complete:
BOOL requestComplete = NO;
id requestResponseObject = nil;
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
requestResponseObject = responseObject;
requestComplete = YES;
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
requestComplete = YES;
NSLog(#"%#", error);
}];
while (!requestComplete)
{
// Tie up the thread, doing nothing...
}
// Proceed

unrecognised selector error for AFHTTPRequestOperation setAuthenticationAgainstProtectionSpaceBlock:

I'm trying to run the following code in IOS.
AFHTTPRequestOperation *requestOperation = [self.httpClient HTTPRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url.text]]
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"reply data = %#", [[NSString alloc] initWithData:operation.responseData encoding:NSUTF8StringEncoding]);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[requestOperation setAuthenticationAgainstProtectionSpaceBlock:^BOOL(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace) {
return YES;
}];
I then get an error like this:
-[AFHTTPRequestOperation setAuthenticationAgainstProtectionSpaceBlock:]: unrecognized selector sent to instance 0x75a81f0
As far as I can see the block that I'm passing has the correct return type and parameters. What am I doing wrong here?
AFURLRequestOperation conditionally compiles with certain delegate callback methods available, depending on whether _AFNETWORKING_PIN_SSL_CERTIFICATES_ is defined or not.
If it is (which is the default when installing from CocoaPods), setWillSendRequestForAuthenticationChallengeBlock: will be available. Otherwise setAuthenticationAgainstProtectionSpaceBlock: and setAuthenticationChallengeBlock: will be available.
setWillSendRequestForAuthenticationChallengeBlock corresponds to connection:willSendRequest:forAuthenticationChallenge:, which is the preferred delegate method to handle challenges.

Kiwi - Expected subject to be nil, got KWAsyncVerifier

I'm using Kiwi to write the tests for my app.
I wrote tests to test against my API. I was guided by this example in the documentation for testing asynchronous calls:
https://github.com/allending/Kiwi/wiki/Asynchronous-Testing
My tests are long, so I made a simplified version of my issue:
describe(#"My Class Name", ^{
context(#"populate", ^{
it(#"download the content", ^{
__block NSString *testResponseObject = nil;
__block NSError *testError = nil;
MyClient *apiClient = [MyClient sharedClient];
NSMutableURLRequest *request = [apiClient requestWithMethod:#"DELETE" path:#"my/path" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
testError = error;
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
[[expectFutureValue(testError) shouldEventuallyBeforeTimingOutAfter(100)] shouldBeNil];
});
});
});
The thing is that if everything works as expected & the operation succeeds the failure block never gets called & instead of nil for NSError I get KWAsyncVerifier.
I'm guessing that's because Kiwi waits for the block where testError is referenced to be executed which never happens & that's why I have KWAsyncVerifier stuck into testError instead of nil.
Is there any alternative how to test this out?
My first recommendation is that you should not test your libraries. From what I read in your example, you are basically checking that AFHTTPRequestOperation is working as documented, but that’s not your responsability to test. You should test that you invoke AFNetworking correctly, and that given an responseObject or an error, your code behaves as you expect.
Anyway, about what you are seeing, you have two “shoulds” in the same line: shouldEventually and shouldBeNil; they use to have beNil matcher, which is unavailable in 2.1, and I think they are bringing back. You can find the discussion in https://github.com/allending/Kiwi/issues/293
Maybe you can try the following to make sure that the failure branch is not taken:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// This will fail if we ever reach this branch
[error shouldBeNil];
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
The shouldEventuallyBeforeTimingOutAfter will keep the test case “alive” waiting to check the response, but if you ever go through the failure branch, the other expectation will fail (and also the one in response will fail after 100 seconds). Hope it helps.

ObjectiveC Blocks and Variables

I have the following method that makes a webservice call from my iOS app (using Restkit)...
BOOL valid = NO;
RKObjectManager *objectManager = [RKObjectManager sharedManager];
NSString *servicePath = [WebServiceHelper pathForServiceOperation:[NSString stringWithFormat:#"/security/isSessionValid/%#", username]];
[objectManager getObjectsAtPath:servicePath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
BooleanServiceResponse *resp = [mappingResult firstObject];
valid = resp.value;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error while validating session for user %# : %#", username, error);
}];
return valid;
However, I get an error on the valid variable, saying it is declared outside the block and is not assignable. I Googled around a bit, and found a recommendation that I declare valid like this instead...
__block BOOL valid = NO;
That gets rid of the error. However, I find that no matter what I set the valid value to within my block, it is not set appropriately when exiting the block. How can I set this value so my method returns the expected value?
I think you don't understand how blocks work. It's not a matter of variable visibility, although __block is correct.
You block is a function executed asynchronously, so valid will be set to resp.value whenever that block is executed, which is very likely to happen later than your return statement.
You need to change your design since so far you are returning an object which is not guaranteed to be set.
EDIT
Example
- (void)didFinishValidation:(BOOL)valid {
// Do whatever you like with the `valid` value
if (valid) {
//...
} else {
//...
}
}
and your blocks become
[objectManager getObjectsAtPath:servicePath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
BooleanServiceResponse *resp = [mappingResult firstObject];
valid = resp.value;
[self didFinishValidation:valid];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error while validating session for user %# : %#", username, error);
}];
In general it's required to define variables as __block in order to mutate them from within a block. The problem here is that the "return" is called before the block is called - the best way to handle this situation is not to define this method as - (BOOL)... but as - (void)... and return the result async via delegation or block callback.