iOS how to use delegate method from within block - ios

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.

Related

AFNetworking - stuck after posting http data

I've inherited some Objective-C code from 2013: so old that it used AFNetworking 1.0!
#implementation AFClaimNotificationAPIClient
+ (AFClaimNotificationAPIClient *)sharedClient {
static AFClaimNotificationAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[AFClaimNotificationAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kClaimNotificationURL]];
});
return _sharedClient;
}
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSMutableURLRequest *request = [self multipartFormRequestWithMethod:#"POST" path:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
}];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
DDLogVerbose(#"%#", request.HTTPBody);
[operation setCompletionBlockWithSuccess:success failure:failure];
[operation start];
}
In the corresponding header file for this class, the AFClaimNotificationAPIClient is defined thus:
#interface AFClaimNotificationAPIClient : AFHTTPClient
and AFHTTPClient no longer exists. It was dropped in AFNetworking 2.0, which came out shortly after this code was written.
After much forum searching, I've actually managed to get it partially working again by upgrading to AFNetworking 2.0, and redefining AFClaimNotificationAPIClient as an AFHTTPSessionManager:
#interface AFClaimNotificationAPIClient : AFHTTPSessionManager
My submit button code now looks like this:
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSURLSessionDataTask *request = [self POST:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
} success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogVerbose(#"Post success");
// handle success
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLogVerbose(#"Post error");
}];
// [operation start];
[request resume]; // [request start] doesn't work
}
I chose AFHTTPSessionManager as the new type for my class because it's the only one that I could find that contains the constructingBodyWithBlock definition, and I was trying to keep the code as close to the original as possible.
Amazingly enough, my reworked code actually posts data to the server and gets a reply. However, the app hangs at that point because the calling code (not shown here) is not receiving a success (or failure) message. I can see that I've removed a whole step from the original code - the setting up of the operation variable and then the triggering of its setCompletionBlockWithSuccess method.
You need to execute the block according to the response passing a responseObject or an error with the corresponding operation
try this
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSURLSessionDataTask *request = [self POST:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
} success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogVerbose(#"Post success");
// handle success
success(nil,responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLogVerbose(#"Post error");
failure(nil, error);
}];
// [operation start];
[request resume]; // [request start] doesn't work
}
You can use AFHTTPRequestOperation as well and this will return both parameters needed in your callbacks

AFNetworking 3.0 AFHTTPSessionManager using NSOperation

I'm stuck now some time and I need help. So in AFNetworking 2.0 we have AFHTTPRequestOperation so I could easily use NSOperationQueue and have some dependencies. So what we have now is only AFHTTPSessionManagerand NSURLSession that does not subclass NSOperation. I have class APIClient that subclasses AFHTTPSessionManager. I am using that class as singleton as sharedClient. I have overriden GET and POST so for example GET looks this:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
NSURLSessionDataTask *task = [super GET:URLString parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
success(task, responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
failure(task, [Response createErrorWithAFNetworkingError:error]);
}];
return task;
}
Do you have any idea how to implement in that manner (if it's possible) to wrap that as NSOperation? So what I want to do - I want to be able to run in parallel two network calls, and after that have another method call that depends on second network call of first two calls. Do you have any idea what would be best approach?
I've written a quick little set of classes (https://github.com/robertmryan/AFHTTPSessionOperation/) that wrap AFHTTPSessionManager requests in asynchronous NSOperation subclass. You can then use that to enjoy maxConcurrentOperation constraints, or operation dependencies.
For example, here's an example where we issue two concurrent requests and have a completion operation dependent upon completion of both of those requests:
// ViewController.m
#import "ViewController.h"
#import "AFNetworking.h"
#import "AFHTTPSessionOperation.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlString1 = #"...";
NSString *urlString2 = #"...";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"AFHTTPSessionManager queue";
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All done");
}];
NSOperation *op1 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:#"GET" URLString:urlString1 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"finished 1");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"failed 1 - error = %#", error.localizedDescription);
}];
[completionOperation addDependency:op1];
NSOperation *op2 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:#"GET" URLString:urlString2 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"finished 2");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"failed 2 - error = %#", error.localizedDescription);
}];
[completionOperation addDependency:op2];
[queue addOperations:#[op1, op2] waitUntilFinished:false];
[[NSOperationQueue mainQueue] addOperation:completionOperation]; // do this on whatever queue you want, but often you're updating UI or model objects, in which case you'd use the main queue
}
#end
It's worth noting that since you're only dealing with two requests, you could also use dispatch groups to accomplish the same thing:
// ViewController.m
#import "ViewController.h"
#import "AFNetworking.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlString1 = #"...";
NSString *urlString2 = #"...";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[manager GET:urlString1 parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"finished 1");
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"failed 1 - error = %#", error.localizedDescription);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[manager GET:urlString2 parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"finished 2");
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"failed 2 - error = %#", error.localizedDescription);
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"All done");
});
}
#end
With dispatch groups, you just need to be careful that every path within both the success and failure blocks call dispatch_group_leave.

AFNetworking Incompatible block pointer types sending

I am using AFNetworking to access a URL with Windows Authentication. I was using ASIHTTPRequest to login like so:
-(BOOL)User:(NSString *)user andPassWordExists:(NSString *)password
{
NSURL *url = [NSURL URLWithString:kIP];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setUseSessionPersistence:YES];
[request setUseKeychainPersistence:NO];
[request setUsername:user];
[request setPassword:password];
[request setDomain:#"domain"];
[request startSynchronous];
NSError *loginError = [request error];
if(loginError == nil){
return true;
}else{
return false;
}
}
and now I am trying to do the same thing with AFNetworking and this what I came up with from this example: http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=18385
-(BOOL)User:(NSString *)user andPassWordExists:(NSString *)password
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:90.0];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceNone]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
return true;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
return false;
}];
[operation start];
}
but this gives me two errors:
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:148:46: Incompatible block pointer types sending 'int (^)(AFHTTPRequestOperation *__strong, __strong id)' to parameter of type 'void (^)(AFHTTPRequestOperation *__strong, __strong id)'
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:152:15: Incompatible block pointer types sending 'int (^)(AFHTTPRequestOperation *__strong, NSError *__strong)' to parameter of type 'void (^)(AFHTTPRequestOperation *__strong, NSError *__strong)'
How Can I create a method that will use user and password to login and store those credentials...this is possible with AFNetworking, it sure was with ASIHTTPRequest
I think you are misunderstanding blocks. You are returning TRUE/FALSE from the callback blocks, not your method. Those blocks will be executed at some point in the future, and their return type is void. Try running this code to see the order:
-(void)User:(NSString *)user andPassWordExists:(NSString *)password
{
// construct your request
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"In success block");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"In Fail block");
}];
[operation start];
NSLog(#"Network op started");
}
As to your design, I would not return true or false, you have to find another way to manage the success and fail scenarios, which could look something like this (I don't know enough about your overall design to give a definitive answer):
-(void)User:(NSString *)user andPassWordExists:(NSString *)password
{
// construct your request
__weak typeof(self) weakSelf = self;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// called at somepoint in the future if the request is success full
[weakSelf handleSuccess:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// called at somepoint in the future if the request fails
[weakSelf handleFail:error];
}];
[operation start];
}
- (void)handleSuccess(id)response {
// process the response
}
- (void) handleFail:(NSError*)error {
// evaluate error
}
You see the bock declaration here: https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPRequestOperation.h

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.

Obj-C: Pass method arguments to nested block

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.

Resources