Question

I have a question. Maybe 2 questions.

First one relates to this piece of code:

-(void)isLoggedIn {
NSString *urlString = [NSString stringWithFormat:@"%@%@%@", kBaseURL, kAPI, kUserIsLogged];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];
}

I would like that this method is not a void, i would like it to be returning a BOOL or something that I can use like: User is logged in > Ok do something somewhere in the app; User is not logged in > Ok prompt the user to a screen where he can introduce his credentials and log in.

Second question is related to this piece of code:

-(void)detail {
NSString *urlString = [NSString stringWithFormat:@"%@%@%@", kBaseURL, kAPI, kUser];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];
}

I would like that this method returns me a NSArray with the parsed JSON, which i can use wherever i want to.

This methods are contained into a Model called NetworkModel (NSObject subclass). I'm just wondering why i should do everything inside the blocks. I should have pieces interacting with network all over the place, and not organized in one unique place/class. Is there a way to do that. I don't want to have 5 lines of requests every ViewController. I want to define methods i can call from any ViewController (obviously importing the header files where needed).

Any suggestions? Thank you in advice!

Was it helpful?

Solution

You can do this with 2 approaches, one change your methods in order to use blocks or use a semaphore to wait for the async request:

With semaphore (THE UGLY WAY)

-(BOOL)isLoggedIn {

__block BOOL isLoggedIn;
 dispatch_semaphore_t waiter = dispatch_semaphore_create(0);

NSString *urlString = [NSString stringWithFormat:@"%@%@%@", kBaseURL, kAPI, kUserIsLogged];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
    isLoggedIn = YES;
    dispatch_semaphore_signal(waiter);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
    isLoggedIn = NO;
    dispatch_semaphore_signal(waiter);

}];

dispatch_group_wait(waiter, DISPATCH_TIME_FOREVER);
return isLoggedIn;
}

The Right Way to do it

-(void)isLoggedIn:(void(^)(BOOL isLoggedIn))completionBlock {
NSString *urlString = [NSString stringWithFormat:@"%@%@%@", kBaseURL, kAPI, kUserIsLogged];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
    completionBlock(YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
    completionBlock(NO);
}];
}

For your second question, you can set a block variable on your networking class that will be called when the request is completed or it fails.

Anyway I think you are not quite familiar with blocks (because of your second question) and I think after a few blocks you will start to like them and you will see that the code is not complicated if you have small snippets of code that perform request & parsing, also you will see that it's soooo easy to debug them and find the problems, I mean, this was also my case when I started to work with blocks.

Using a block variable

@interface YourNetworkClass:NSObject {
void(^resultHandlerBlock)(NSArray *results); //this block has as param a NSArray, you can change it to whatever you want
}

-(void)setResultHandlerBlock:(void(^)(NSArray *array))handlerBlock;

@implementation YourNetworkClass

-(void)setResultHandlerBlock:(void(^)(NSArray *array))handlerBlock {
resultHandlerBlock = block;
}

-(void)anotherMethodThatDoesNetworkOperationsAndReturnsAnArray {
 ....
 resultHandlerBlock(arrayOfResults);
}


@interface AViewControllerThatUseNetwork: UIViewController {
}

@implementation AViewControllerThatUseNetwork 

-(void)initNetworkClass {
  YourNetworkClass *networkClass = [[YourNetworkClass alloc] init]; //or other init methods
  [networkClass setResultHandlerBlock:^(NSArray *results) {
   //do whatever you want with the array
}];
}

OTHER TIPS

I asked something similar a while back. I believe you'll find your solution there. Passing blocks to a AFNetworking method?

In general, you'll need to change the way you think you should get the data from "if logged in return BOOL" to "check if I can log in, if that fails do something within the AFNetworking block.

The link above also answers your second question.

Your methods should take a block (or two) that will be executed from the AFNetworking success (or failure) blocks. This approach embraces the asynchronous nature of what you're trying to do. You can't add return values to the methods because of this asynchronicity.


So, you would change your method to:

-(void)isLoggedInWithCompletion:(LoginCompletionBlock)completion {
    ...

    [manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"JSON: %@", responseObject);
        completion(/* whatever parameters your block specifies */);
    } ...
}

So, little update on the situation (thanks of all the precious suggestions..thanks guys!). Now my code looks like this (in my NetworkModel.m, obviously I have added the method to NetworkModel.h so that I can call it anywhere):

-(void)isLoggedIn:(void(^)(BOOL isLoggedIn))completionBlock {
NSString *urlString = [NSString stringWithFormat:@"%@%@%@", kBaseURL, kAPI, kUserIsLogged];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
    if ([[NSString stringWithFormat:@"%@", responseObject] isEqualToString:@"true"]) {
        completionBlock(YES);
    } else {
       completionBlock(NO);
    }
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
    completionBlock(NO);
}];
}

In my AppDelegate.h I've included my NetworkModel.h and in my AppDelegate.m I now have the following code:

[[self model] isLoggedIn:^(BOOL isLoggedIn) {
    if (isLoggedIn == YES) {
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"Start01"];
        [self.window setRootViewController:controller];
    } else {
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"Start00"];
        [self.window setRootViewController:controller];
    }    
}];

And it works kinda fine. But now I have another question about this. When I run the app, there is something weird happening. As soon as it retrieves the information about the user login status it jumps from VC (the designated InitialView Controller - Start00 on my Storyboard) to VC (loggedInVC - Start01 on my Storyboard). Is there a way not to show this kinda jump to the user? Or this is a normal behaviour? Now user sees for a second the first screen and then the screen he should actually see (cause he's logged in).

But I guess the easiest way to work with blocks and all this is to use them right where I need them. Instead of trying to encapsulate them into methods or classes or whatever. As soon as data is there, I'll do what i have, directly inside the success or failure block. Am I thinking right?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top