How do I access an AFHTTPRequest's Completion Block? - objective-c

I'm posting a notification in the request failure block:
[manager POST:path
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (operation.response.statusCode == 200) {
//message delegate
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:HOST_UNREACHABLE object:operation];
}];
In the method that receives the notification, the completionBlock attribute is nil.
How do I access it without subclassing & overriding?

First to answer the question of how to send a block to an NSNotification:
The way you're attempting to do it is dangerous, because we don't know how AFHTTPSessionManager handles the blocks you pass it, and, unless its in the public interface, what it does may not remain fixed over time.
So, make a local variable to represent the block you want to pass, say, the completionBlock...
// this is a local variable declaration of the block
void (^completionBlock)(AFHTTPRequestOperation*,id) = ^(AFHTTPRequestOperation *operation, id response) {
if (operation.response.statusCode == 200) {
//message delegate
}
};
[manager POST:path
parameters:nil
success:completionBlock
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:HOST_UNREACHABLE object:completionBlock];
}];
The observer can get the block and invoke it this way...
- (void)didReceiveNotification:(NSNotification *)notification {
void (^completionBlock)(AFHTTPRequestOperation*,id) = notification.object;
// use it
[manager POST:someOtherPath
parameters:nil
success:completionBlock
// etc.
];
}
But I think the approach is strange. It spreads out responsibility for making the request to the object that gets notified, leaving it needing to know the path to retry the parameters (which you don't have in this case, but you might one day).
Consider instead subclassing the manager and adding behavior that does the retry. Now your manager subclass can be in charge of all requests, including retries, and your other classes are just customers who handle the outcome. Something like...
#interface MyAFHTTPRequestManager : AFHTTPSessionManager
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
retryURL:(NSString *)retryURLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask *task, NSError *error))failure;
#end
Have your subclass implementation call super with the first URLString, and upon failure, call super with the retryURLString. e.g.
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
retryURL:(NSString *)retryURLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask *task, NSError *error))failure {
[super POST:URLString parameters:parameters success:success
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[super POST:retryURLString parameters:parameters success:success failure:failure];
}];
}

Related

Value method called not returned

I have this class that performs a REST call .
- (NSString*)cerca:(NSString*)email{
if ([[Utility sharedUtility] connected]) {
HttpClient *self = [HttpClient sharedHTTPClient];
[self.requestSerializer setValue:[self getUserAgent] forHTTPHeaderField:#"User-Agent"];
[self.requestSerializer setValue:NST_CODE forHTTPHeaderField:#"Nst-Code"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"nst_id"] forHTTPHeaderField:#"Nst-Id"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"api_key"] forHTTPHeaderField:#"Api-Key"];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"email"] = email;
__block NSString *result;
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
result = responseObject;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
return result;
}
The call returns a JSON . My problem is that when I do return result ; Nothing is passed to the calling method .
Can you tell me why ??
The calling method is
HttpClient *client = [HttpClient alloc];
NSString *result = [client cerca:email];
That method you are calling is an asynchronous call, meaning the result comes after you have returned. You need to change your method to accept a block argument and return the result in the callback.
- (void)cerca:(NSString*)email callback:(void (^)(id result)) callback {
if ([[Utility sharedUtility] connected]) {
...
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
if (callback) {
callback(responseObject)
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
}
To call the method you would do:
[client cerca:email completion:^(NSString *response) {
// Do what you want with the response.
}];
Its because the self POST call is asynchronous and you are returning the result before it has had time to be assigned. You need to rebuild your - (NSString*)cerca:(NSString*)email method so it somehow can handle the asynchronousness of this. Easiest way is usually to change the method so it takes a completion block as in parameter. Maybe something like:
- (void)cerca:(NSString *)email completion:(void (^)(NSString *res))completion {
[self POST:#"get_info" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(responseObject);
}
}failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion){
completion(nil);
}
}];
}
I changed my method
- (NSString*)cerca:(NSString*)email completion:(void (^)(NSString *res))completion {
But I do not understand what to call it . Indications ?
[client cerca:email completion:nil];

return data from AFHTTPRequestOperation?

I'm moving my app code to an MVC model and so I created a method to retrieve some data from an API.
+ (NSMutableArray *)loadFromFeed {
NSString *feed = #"https://api.test.com";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedUrl]];
request = [mutableRequest copy];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [JSONResponseSerializerWithData serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"items"];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
CLS_LOG(#"Error");
}];
}
Now, ideally, I'd like to return jsonArray as part of this method. However, since AFHTTPRequestOperation is asynchronous, I don't know how to solve this and still be able to call [Data loadFromFeed]; anywhere in the app. How can I do this?
You could pass two block named success and failure to loadFromFeed ,
and then call the two block from your setCompletionBlockWithSuccess success and failure block, passing jsonArray to the success block:
typedef void (^Success)(id data);
typedef void (^Failure)(NSError *error);
- (void)loadFromFeed:(Success)success failure:(Failure)failure;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"items"];
success?success(jsonArray):nil;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure?failure(error):nil;
}];
then use in this way:
[Data loadFromFeed:^(id data) {
NSLog(#"%#",data)
} failure:^(NSError *error) {
NSLog(#"%#",error)
}];];

Afnetworking wrong block is returned

I am using afnetworking and AFHTTPRequestOperationManager,
I have a singleton class, which contains all my api call. However, when I have concurrent api call, wrong data is being returned. API call A is returning API call B response?
CHAFHTTPRequestOperationManager is a subclass of AFHTTPRequestOperationManager
Anyone experience the same problem, what do I need to do to solve this:
NSString *path = [NSString stringWithFormat:#"users/%#/profile_photo", userName];
CHAFHTTPRequestOperationManager *manager = [CHAFHTTPRequestOperationManager sharedManagerObj];
[manager GET:path
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}
];
}

How to reuse obj-c block?

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://aaa"
success:^(AFHTTPRequestOperation *operation, id responseJSON) {
...
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[TWMessageBarManager sharedInstance]
showMessageWithTitle:#"Network connection failure"
description:#"Please check your network"
type:TWMessageBarMessageTypeError];
}];
Some block is constant, can be used repeatedly. For example here's failure block, How can I reuse this block for reduce the amount of code?
I hope it is a global reuse, rather than the current context, so I can store it as a property? Or get_method()?
Save the block to a variable, then you can pass that around:
void (^failureBlock)(AFHTTPRequestOperation *operation, NSError *error) = ^void(AFHTTPRequestOperation *operation, NSError *error) { /* write what you want */ };
void (^successBlock)(AFHTTPRequestOperation *operation, id responseJSON) = ^void(AFHTTPRequestOperation *operation, id responseJSON) { /* write what you want */ };
Then you can use it in further calls like this:
[manager GET:#"" success:successBlock failure: failureBlock];
Bonus: Check out this guide.
you can save it like a variable like so:
void(^blockname)(AFHTTPRequestOperation*, NSError*) = ^(AFHTTPRequestOperation *operation, NSError *error) {
[[TWMessageBarManager sharedInstance]
showMessageWithTitle:#"Network connection failure"
description:#"Please check your network"
type:TWMessageBarMessageTypeError];
}
then just put blockname for the failure parameter instead of the whole thing
Another approach, instead of reuse blocks, you should consider reuse the whole function
- (void)getURLPath:(NSString *)urlPath withSuccessBlock:(void (^)(id responseJSON))block {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlPath
success:^(AFHTTPRequestOperation *operation, id responseJSON) {
...
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[TWMessageBarManager sharedInstance]
showMessageWithTitle:#"Network connection failure"
description:#"Please check your network"
type:TWMessageBarMessageTypeError];
}];
}

Why does my test fail with "expected method was not invoked" when using a real parameter but pass when using `[OCArg isNotNil]`?

I'm testing a method using OCMock. The method is as follows:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
block:(void (^)(id responseObject, NSError *error))block
{
return [self GET:URLString parameters:parameters success:^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
} failure:^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
}];
}
This test fails with "expected method was not invoked":
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
void (^successBlock)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
};
void (^failureBlock)(NSURLSessionDataTask *, NSError *) = ^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:successBlock failure:failureBlock];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
But this passes:
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
Why does the first test fail and how can I make it pass?
I've put an example project on GitHub demonstrating the issue: https://github.com/paulyoung/OCMockExample
As others have said, blocks are compared by comparing their addresses. Two different blocks - even though they do the exact same thing - are not equal.
You can still make your test more specific by actually invoking the success and failure block, and check if they behave as expected.
As far as I understand you want to test that
the method calls another method with two blocks, that when invoked, invoke the original block with
some response object as the first and nil as the second argument in case of success
nil as the first, an NSError* as the second argument in case of failure
Here is the test for the success case:
id responseMock = #"responseObject";
__block BOOL blockCalled = NO;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {
XCTAssertNil(error, #"");
XCTAssertEqualObjects(responseObject, responseMock, #"");
blockCalled = YES;
};
[[[sessionManagerPartialMock expect] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSURLSessionDataTask *, id);
[invocation getArgument:&successBlock atIndex:4];
successBlock((id)#"task", responseMock);
}] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
XCTAssert(blockCalled, #"");
I checked your repo and I think now I can give you good answer.
What you are trying to do in your test is blocks comparing and currently this is not possible in Obj-c world. Even if you block from test and block created inside singleton looks identical they are not the same blocks.
You can fix this by using you fail and success block as private properties and use them in your OCMock expect. Like :
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:sessionManager.successBlock failure:sessionManager.failureBlock];