Method with completion handler - ios

I've created a method in a class:
- (void)getTableData:(NSString *)URL withCompletionHandler:(void (^)(NSString *))handler{
__block NSDictionary *JSON;
[manager POST:urlString parameters:jsonDict success:^(AFHTTPRequestOperation *operation, id responseObject){
JSON = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&error];
handler(JSON);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#",error);
// handle failure
}];
}
and call it in another class by
[ObjOfSecondClass getTableData:BILL withCompletionHandler:^(NSString* returnString)handler{
}];
It shows Expected expression error at handler.

It's an expression error because you are using it in wrong way.
Try this one in viewDidLoad
[ObjOfSecondClass getTableData:BILL withCompletionHandler:^(NSString* returnString){
}];
handler is used is block implementation to return value from where they are called.
Note - replace string to dictionary in block definition because you are getting dictionary from API not string.
Learn block syntax

Related

Variable not changed by AFNetworking GET function

I am trying to use an AFNetworking class to retrieve data from a database. Long story short, the data received from the parameter responseObject is filled with items. Here is my problem however. I am trying to copy the results in responseObject into an NSDictionary called results. I used the following code to get there:
__block NSDictionary *results;
[manager GET:#"http://daneolog.altervista.org/app/getData.php"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) { results = responseObject;
NSLog(#"Inside: %#", results); }
failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(#"%#", error); }];
NSLog(#"Outside: %#", results);
I tried NSLoging the results dictionary INSIDE the success braces, and everything is a-okay there.
I tried NSLoging the results dictionary OUTSIDE the GET function, and it comes up as (null).
These are my results:
2015-11-12 14:34:34.875 TestApp[4864:258743] Outside: (null)
2015-11-12 14:34:35.242 TestApp[4864:258743] Inside: (
{
address = "Sample Address";
}
)
Now notice the peculiar thing: the outside NSLog is being executed first. I don't know why this is so. Can anyone help me on this? Thanks a bundle.
Your code outside the success block is executing before the success block completes that is why results come up as null. results is on the main thread, and you are not blocking (as is correct with an HTTP request).
You can pass a weak reference to an object, and have that updated. Or if you absolutely need to wait for the result (login for instance) then you should do it on the main thread.
Here is an example of a weak object:
//results is an object created earlier
__weak NSDictionary *weakResults = results;
[manager GET:#"http://daneolog.altervista.org/app/getData.php"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
weakResults = responseObject;
NSLog(#"Inside: %#", results);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
//when the success block finishes the results object will be populated
If you wanted to block i.e. do it on the main thread you could use:
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
However, if the response doesn't come back, your app hangs.

How to maintain proper encapsulation for singleton created with data

I have a singleton networking class as well as a singleton object that needs to persist throughout my app. The singleton is initialized based on data retrieved from a web call, so right now my code works, and I have the following in my singleton networking class:
- (void)initializeObjectWithSuccess:(void (^)(BOOL))success
failure:(void (^)(NSError *error))failure {
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
[[CPList sharedList] setIdentifier:json[#"id"]];
[[CPList sharedList] setImages:json[#"images"]];
if (success) {
success(YES);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
I don't know how to initialize all the properties I need on my singleton CPList without setting them within this method, however I know that this is not proper encapsulation because the CPRequestManager Class should know nothing about the CPList Class
If your issue is that you don't want this class to know the name of CPList and the detail that it's a singleton and that it can access it with +[CPList sharedInstance] then you can just pass in an object that conforms to a protocol. This basically moves the knowledge of the singleton somewhere else
- (void)initializeObjectWithList:(id<CPList>)list
success:(void (^)(BOOL))success
failure:(void (^)(NSError *error))failure;
{
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
[list setIdentifier:json[#"id"]];
[list setImages:json[#"images"]];
if (success) {
success(YES);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Or you could remove all knowledge that there is a "list" and just have this method return the actual data and then the caller can set it on the list
- (void)initializeObjectWithSuccess:(void (^)(NSString *ID, NSArray *images))success
failure:(void (^)(NSError *error))failure;
{
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
if (success) {
success(json[#"id"], json[#"images"]);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Without any further context it's hard to suggest structural changes but here's two potential refactoring that might get you thinking about what you coudld do

Returning a BOOL method inside a block?

I have this bool method that returns a yes or no for an inputted string.
I'm successfully able to return a YES or a NO, but I cannot seem to able to make a network connection and return a YES or a NO depending on the server's response.
I tried using __block and I don't feel like that will wait for the web request to finish, is there a way to return YES or NO in the success block without it giving me the error:
Incompatible block pointer types sending 'BOOL(^)(NSURLSessionTask*__strong, NSError __strong' to parameter of the type 'void(^)(NSURLSessionTask...)
-(BOOL)customResponseForString:(NSString *)text {
__block BOOL response_available;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/plain"]];
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//return NO;
}];
});
return response_available;
}
Your block definition syntax is probably erroneous, because you can definitely return a BOOL along other parameters in a block.
- (void)fetchCurrentUserWithCompletion:(void (^)(BOOL success, User *user))completion;
This method would be called like this:
[self.userProfileController fetchCurrentUserWithCompletion:^(BOOL success, User *user) {
if (success) {
NSLog(#"Current User Name: %#", user.fullName);
}
}];
If you use AFNetworking, check the AFHTTPRequestOperation object that handle completionBlocks:
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [self userFromResponseObject:responseObject];
if (completion) completion(YES, user);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) completion(NO, user);
}];
Because you are implicitly initializing response_available to NO and then using an async GCD call, your method as written will always immediately return NO without waiting for the request to finish. Note: switching to dispatch_sync won't help either because AFNetworking will queue the GET request asynchronously either way.
Best Approach
Add a completion block argument to customResponseForString:. Then simply execute your completion block in the success or failure blocks of the AFHTTPRequestOperation.
Workable Approach (use caution!)
It is possible to make customResponseForString: wait for a response to the network request, but you will have significant issues if it is ever called from the main thread.
First you create a dispatch group and tell it you are starting some long-running work:
dispatch_group_t networkGroup = dispatch_group_create();
dispatch_group_enter(networkGroup);
Then you need to make your network request and when it completes tell the group that the work is finished with dispatch_group_leave():
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
dispatch_group_leave(networkGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
response_available = NO;
dispatch_group_leave(networkGroup);
}];
Before your original method returns, tell it to wait for the entire group to finish processing:
dispatch_group_wait(networkGroup, DISPATCH_TIME_FOREVER);
return response_available;
You could adjust this time interval as needed or leave it at DISPATCH_TIME_FOREVER to let the network request time out on its own.

AFNetworking callbacks - delegate or notification?

I have a question on which is best way or the correct way to send AFNetworking results to controller. Is it via delegate or notification?
I created a class to handle make API calls that has the code below. So if imported this class to another controller and call this method to make API call. Should I do delegate or notification?
I have read www.raywenderlich.com/59255/afnetworking-2-0-tutorial and it is using delegates. I also been watched CodeSchool tutorial, which they used notification from Model to Controller.
I added the code below in a hope to better show my question.
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
// notification way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil
userInfo:responseObject ];
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil ];
}];
// delegate way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
if ([delegate respondsToSelector:#selector(getUserFeedsDidFinish:resultDict:)])
{
[delegate performSelector:#selector(getUserFeedsDidFinish:resultDict:) withObject:self withObject:resultDict];
}
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
if ([delegate respondsToSelector:#selector(getUserFeeds:didFailWithResultDict:)]) {
[delegate performSelector:#selector(getUserFeeds:didFailWithResultDict:)
withObject:self
withObject:[NSDictionary dictionaryWithObject:error.userInfo forKey:KEY_ERRORS]];
}
}];
I will recommend use blocks, how? I will write a service for you, this one is wrote in a class called Connection:
+(void)requestLocation:(NSString*)googleReference completionBlock:(void (^)(NSString * coordinates, NSError * error)) handler{
NSString * urlString = #"https://maps.googleapis.com/maps/";
NSMutableDictionary * parametersDictionary = [NSMutableDictionary dictionary];
[parametersDictionary setObject:googleReference forKey:#"reference"];
[parametersDictionary setObject:#"true" forKey:#"sensor"];
[parametersDictionary setObject:#"key(it is not)" forKey:#"key"];
AFHTTPClient *HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:urlString]];
NSURLRequest *URLRequest = [HTTPClient requestWithMethod:#"GET" path:#"api/place/details/json" parameters:parametersDictionary];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:URLRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError * error = nil;
NSDictionary * response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
NSDictionary * dicGeo = [((NSDictionary*)[response objectForKey:#"result"]) objectForKey:#"geometry"];
NSDictionary * coords = [dicGeo objectForKey:#"location"];
NSNumber * lat = [coords objectForKey:#"lat"];
NSNumber * lng = [coords objectForKey:#"lng"];
NSString * coordinates = [NSString stringWithFormat:#"%#,%#", lat.description, lng.description];
handler(coordinates, error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[requestOperation start];
}
Then to call this service:
[Connection requestLocation:#"google reference (it is not)" completionBlock:^(NSString *coordinates, NSError *error) {
//Your code with results.
}
I've only scratched the surface with AFNetworking. From what I've seen, most of it seems to use a third approach, blocks.
Blocks are somewhat new, and different than both delegates and notifications.
Blocks are an extension to C function pointers that let you pass code into a method when you call it.
A common design pattern using blocks is to create a method that takes a completion block. A completion block is a piece of code that gets invoked when an async request is completed.
Take the AFNewtworking method HTTPRequestOperationWithRequest as an example. That method takes a success block, that gets called if the request succeeds, and a failure block, that gets called if the request fails.
Block is the easiest way to use IMO. You don't need to implement extra delegate methods or you don't need any conformations.
Basically define your wrapper like this.
typedef void(^SampleRequestCompletion)(NSError *error, id data);
- (void)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
completion:(SampleRequestCompletion)completion
{
[self GET:URLString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Do what you want
if (completion) {
completion(nil, data);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Failure case
if (completion) {
completion(error,nil);
}
}];
}
And call this method from any objects like this,
[self GET:path parameters:dictionary completion:^(NSError *error, id data) {
}];
So you can manage what to do whenever the call ends with success or failure.
As the tutorial recommended, we can extract the web service related code into a module which acts more like a model level thing. Considering the communication between the network module and views, view invoke/start the request on a singleton web service client, once response back the usual workflow would be send the result to view controller and show the data in the views. We don't need to return anything back to network module.
So this workflow is more like a notification than delegation. And set the V as the M's delegate, it's weird.
Notification : Hey, man, I have done my job, it's your turn.
Delegation: Hey, man, I have done lots, now I need you cover/back up/provide me some tasks, then I will continue/complete the work.
In some situations, it's difficult to choose which one better. For AFNetworking, I thought the Notification approach better.

Passing JSON object received asynchronously in block to an instance variable - using AFNetworking on iOS6

This code has the JSON but no matter what I do I haven't been able to figure out how to pass this JSON to an instance variable. Each time I set it to a variable and call that variable outside of this method its nil. So what I can gather is that the variable is called before the following async call returns.
So the question is what can I do to the following code so that I can extract the JSON value. Somewhere on the internet I read that I would need to pass it a block which would server as a call back on completion but I cannot figure out how to do that for the following code
//Gets the JSON object that contains the entries from the server
-(void)getEntriesFromServer
{
NSLog(#"%s", __PRETTY_FUNCTION__);
[[appAPIClient sharedClient] getPath:#"/entries"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id JSON)
{
NSLog(#" JSON array = %#",[JSON valueForKeyPath:#"entries"]);
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#" Json not received");
}];
}
Thanks
try to add __block to definition of an array e.g __block NSArray* entriesArray;
or make a property like
#property (nonatomic, retain) NSArray* entriesArray;
and change your code like this
[[appAPIClient sharedClient] getPath:#"/entries"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id JSON)
{
//NSLog(#" Json value received is : %# ",[JSON description]);
//NSLog(#" JSON array = %#",[JSON valueForKeyPath:#"entries"]);
self.entriesArray = [NSArray arrayWithArray:[JSON valueForKeyPath:#"entries"]];
NSLog(#" JSON array from inside block = %#", _entriesArray);
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#" Json not received");
}];
Hope it'll help

Resources