Obj-C: Pass method arguments to nested block - ios

I am using AFNetworking to consume some JSON data. I have to do this in a number of my view controllers. I'm trying to refactor my code and remove some duplication. I would like to know how I could pass, for instance, a NSArray object to the completion block of AFHTTPRequestOperation?
I have attempted the following to no avail.
-(void)request:(NSArray *)jsonArray
{
// ...
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation, id responseObject) {
jsonArray = responseObject;
dispatch_async(dispatch_get_main_queue(),
^{
[self.tableView reloadData];
}
);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed: %#, %#", error, error.userInfo);
}
];
[operation start];
}

It sounds like you want jsonArray to be an out method parameter so you can set its value within your method. With an asynchronous request like you have here this is heavily discouraged, but nonetheless.
Your method definition would change to:
-(void)request:(out NSArray **)jsonArray;
Calls to this method would change to:
[object request:&anArray];
And your completion handler would set the array with:
*jsonArray = responseObject;
For what it's worth, I agree with CrimsonChris who suggested using a callback block to handle this properly.

In case I understand you, I can advice you to do like this:
-(void)request:(NSArray *)jsonArray
compltition:(void(^)(NSDIctionary *response))complition {
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation, id responseObject) {
jsonArray = responseObject;
dispatch_async(dispatch_get_main_queue(),
^{
//returning response
complition(response);
[self.tableView reloadData];
}
);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed: %#, %#", error, error.userInfo);
}
];
[operation start];
}
and where you are calling this function:
[self request:jsonArray complition:^(NSDictionary *response) {
NSLog(#"Response = %#, response")
//and you can parse and reload your table
}];

I solved this by defining the function in a helper class, making jsonArray an instance variable of that class, instead of a function argument.

Related

Ios-Afnetworking multiple download using selector method

Handle single download via afnetworking is good my question is that how handle multiple click on different button then it call this method then process break previous.
it is bcoz suppose several button hit at time then it confuse to download. how handle multiple download in selector method,if in array of batch download then it's easy but through which how .
-(void)downloadimagefromserver:(UIButton *)sender
{
int index =(int) sender.tag;
historyclass *class1 = [messages objectAtIndex:index];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:arrayOfStringsfinal[1]]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"success");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];
}

Run request operations in loop inside operation

How can I run multiple requests inside the success block of 1 request and wait for it to finish?
[manager GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%# Response: \n%#", url, responseObject);
resultsArray = [[NSMutableArray alloc] init];
for (NSDictionary *json in [responseObject objectForKey:#"items"]) {
[self getDetails:json];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[SVProgressHUD dismiss];
}];
Where in getDetails:(id)json is the method to load the group of requests whose parameters are based on the result of the main request.
For example:
I would like to request from the API a list of students, then on the success block. For each student, I would like to get the related data from another table (another request) and put them on my NSObject.
EDIT Here is my getDetails method
- (AFHTTPRequestOperation *)getDetails:(NSDictionary *)json
{
NSLog(#"Start Op %#",[json objectForKey:#"related_salon"]);
NSString *url = [NSString stringWithFormat:#"%#read/salons/%#",SERVER_API_URL,[json objectForKey:#"related_salon"]];
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:req];
//op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success %#",[json objectForKey:#"name"]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed Op %#",error.localizedDescription);
}];
//AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:req];
//op.responseSerializer = [AFJSONResponseSerializer serializer];
[op start];
return op;
}
The AFNetworking GET method returns a ATHTTPRequestOperation (an NSOperation subclass). You could have your getDetails method return that object. You can then create a new operation dependent upon those operations, which you'd run at the end:
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// add here whatever you want to perform when all the getDetails calls are done,
// e.g. maybe you want to dismiss your HUD when all the requests are done.
[SVProgressHUD dismiss];
}];
[manager GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%# Response: \n%#", url, responseObject);
resultsArray = [[NSMutableArray alloc] init];
for (NSDictionary *json in [responseObject objectForKey:#"items"]) {
NSOperation *operation = [self getDetails:json];
[completionOperation addDependency:operation];
}
[[NSOperationQueue mainQueue] addOperation:completionOperation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[SVProgressHUD dismiss];
}];
Again, this is presuming that getDetails is doing its own GET call, and that you change getDetails to (a) capture the NSOperation returned by GET and (b) return it.

AFNetworking 2 POST with Authentication challenge

Im using AFNetworking 2, with AFHTTPRequestOperation I can use for my Get
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"GET"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.securityPolicy = securityPolicy;
[operation setWillSendRequestForAuthenticationChallengeBlock:
^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) {
//the certificate
}
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
DLog(#"operation :: %#", responseObject);
NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
DLog(#"operation :: %#", result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
DLog(#"operation error :: %#", error);
}];
[operation start];
But now i need to use POST, with parameters,
I have problems finding how to set parameters on
AFHTTPRequestOperation
or finding how to set challenge block for
AFHTTPRequestOperationManager
how to have a POST with parameters and challenge block?
cheers
I am working now on the POST request. So far I've came up with the following code while trying to send an NSDictionary with POST method:
NSDictionary*packet = [NSDictionary dictionaryWithObjectsAndKeys: ......
[manager POST:path
parameters:packet
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if ([responseObject isKindOfClass:[NSDictionary class]])
[self parseReceivedDataPacket:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Actually it works for me, apart from getting an "unacceptable content-type: text/html" when sending this data. But it gets received.
Hope this was useful.

iOS how to use delegate method from within block

I am using AFNetworking for request/response tasks of my application. In one of my handler classes, when an HTTP request succeeds, I must perform a call to delegate method in order to update the UI (note that I specifically do not want to use NSNotificationCenter). However, the compiler complains about the delegate not being declared. I must keep overlooking it somehow and make the same mistake. Here is the simple version of my problematic code:
//MYHandler.h
#protocol MYDelegate <NSObject>
-(void) myDelegateMethod;
#end
#interface MYHandler : NSObject
#property (strong, nonatomic) id<MYDelegate> delegate;
#end
//MYHandler.m
#implementation MYHandler
-(void) doSomething
{
//Create and configure request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess: successBlock failure:failureBlock];
[operation start];
}
//Success and Failure
typedef void(^SuccessBlock)(AFHTTPRequestOperation *operation, id responseObject);
typedef void(^FailureBlock)(AFHTTPRequestOperation *operation, NSError *error);
SuccessBlock successBlock = ^(AFHTTPRequestOperation *operation, id responseObject) {
[_delegate myDelegateMethod];
//^this says _delegate is an undeclared identifier
};
FailureBlock failureBlock = ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
};
#end
//MyViewController.h
#interface MyViewController : UIViewController <MYDelegate>
#end
//MyViewController.m
-(void) myDelegateMethod
{
//
}
You are outside of any method. It's like you're trying to do this:
//MYHandler.m
#implementation MYHandler
-(void) doSomething
{
//Create and configure request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess: successBlock failure:failureBlock];
[operation start];
}
[_delegate myDelegateMethod];
#end
Clearly this won't work.
You could instead create methods that return those blocks:
#implementation MYHandler
typedef void(^SuccessBlock)(AFHTTPRequestOperation *operation, id responseObject);
typedef void(^FailureBlock)(AFHTTPRequestOperation *operation, NSError *error);
- (void(^)(AFHTTPRequestOperation *operation, id responseObject))success
{
return ^(AFHTTPRequestOperation *operation, id responseObject) {
[_delegate myDelegateMethod];
};
}
- (void(^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
return ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
};
}
-(void) doSomething
{
SuccessBlock successBlock = [self success];
FailureBlock failureBlock = [self failure];
//Create and configure request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess: successBlock failure:failureBlock];
[operation start];
}
#end
You are trying to access the delegate variable from outside the scope of the class. Try this:
//MYHandler.m
-(void) doSomething
{
//Success and Failure
typedef void(^SuccessBlock)(AFHTTPRequestOperation *operation, id responseObject);
typedef void(^FailureBlock)(AFHTTPRequestOperation *operation, NSError *error);
SuccessBlock successBlock = ^(AFHTTPRequestOperation *operation, id responseObject) {
[_delegate myDelegateMethod];
//^this says _delegate is an undeclared identifier
};
FailureBlock failureBlock = ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
};
//Create and configure request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess: successBlock failure:failureBlock];
[operation start];
}
In your case the parameter you call _delegate is not in the scope and can not be used at all. To solve this you might want to declare the block itself where this parameter is in the scope. For instance declare it in the method where you pass the block to the request method.
Since this procedure is actually more common then yours where you declare the block outside the class implementation I can assume there is a reason you moved it outside. If this is for some reason mandatory you could use a macro #define successBlock ^(AFHTTPRequestOperation... which will at compile time insert the code for you. Doing so will insert the scope where you use the block as well.
Why this happens is you need to think of block just the same as C/C++ functions, if you want to use the _delegate you also need to insert it as an input parameter next to AFHTTPRequestOperation *operation, id responseObject in one of your cases.
Why this actually works without an explicit parameter declaration is because the compiler already kind of does that for you but still, it does need that object to be in the scope.

AFNetworking: set GET params *and* intercept redirects

I'm using AFNetworking 2.0 in an iOS project, and I'm trying to build a GET request with some parameters, and intercept redirects.
I see the method -[AFHTTPRequestOperation setRedirectResponseBlock], which I'm using to grab the redirects and do something with them. But I don't see how to set the request parameters on that operation. Here's what that looks like:
AFHTTPRequestOperation *ballotOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
[ballotOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"in completion");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"in error");
}];
[ballotOperation setRedirectResponseBlock:^NSURLRequest *(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse) {
if (redirectResponse == nil) {
return request;
} else {
NSLog(#"in redirect, blocking");
[ballotOperation cancel];
return nil;
}
}];
[[AFHTTPRequestOperationManager manager].operationQueue addOperation:ballotOperation];
I see AFHTTRequestOperationManager has the method GET:parameters:success:failure: in which you can set the parameters. But that starts the request immediately, not giving me a chance to set the redirect block on it.
I see some sample code out there from AFNetworking 1.x using AFHTTPClient, but I don't want to go back!
How can I do what I'm trying to do?
The [AFHTTPRequestOperationManager GET...] method in AFHTTPRequestOperationManager.m is just a wrapper around creating a AFHTTPRequestOperation object and adding it to the operationQueue. Using this as an example, you can accomplish what you are trying to do.
This is how the request is created in the GET method of AFHTTPRequestOperationManager:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:#"GET" URLString:[[NSURL URLWithString:URLString relativeToURL:manager.baseURL] absoluteString] parameters:parameters error:nil];
where urlString is a NSString representing the url, and parameters are an NSDictionary.
I believe the rest of your code should work, but just in case, here is how it is done in the GET method (with your redirect block added as well):
AFHTTPRequestOperation *ballotOperation = [self HTTPRequestOperationWithRequest:request success:success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"in completion");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"in failure");
}];
[ballotOperation setRedirectResponseBlock:^NSURLRequest *(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse) {
if (redirectResponse == nil) {
return request;
} else {
NSLog(#"in redirect, blocking");
[ballotOperation cancel];
return nil;
}
}];
[manager.operationQueue addOperation:ballotOperation];

Resources