Thursday, April 3, 2014

Blocks in Web Service Functions

Hi Everyone! It has been a very long while since I last posted in this blog. This time, I will be making a tutorial in using blocks when we are accessing our API or web services.

Disclaimer: I am also not that 'expert' when it comes to blocks. But I have learned this method throughout my career as an iOS Developer. This is a combination of what I learned from my senior teammates.

By the way, I am using the AFNetworking Framework.

Set up...


1. Create a singleton-patterned APIClient class which is a subclass of AFHTTPClient.

2. Our APIClient interface file should look like this...
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"

@interface APIClient : AFHTTPClient

+ (APIClient *)sharedClient;


@end

3. And in our implementation file:
#import "APIClient.h"
#import "Constants.h"

@implementation GCAPIClient

/**
We usually have our Development and Production API links, right? So to cater the changes in our Development and Production URLs, let's do this #if #else #endif preprocessor directives.

DevelopmentAPIURL and ProductionAPIURL values are defined in my Constants.h file
Example in Constants.h file:
#define DevelopmentAPIURL @"https://jescdev.sample.com/api/" 
#define ProductionAPIURL @"https://jesc.sample.com/api/"
*/
+ (APIClient *)sharedClient 
{
    static APIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
#if STAGING
        _sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:DevelopmentAPIURL]];
#else
        _sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:ProductionAPIURL]];

#endif
    });
    
    return _sharedClient;
}

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (!self) {
        return nil;
    }
    
    return self;
}

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
    NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
    [request setTimeoutInterval:120];
    return request;

}

@end

4. Next, I will setup my RequestManager class which is an NSObject subclass. So in my RequestManager.h file:
#import <Foundation/Foundation.h>
#import "AFJSONRequestOperation.h"

typedef void(^ResultBlock)(id results);
typedef void(^FailedBlock)(NSError *error);

@interface RequestManager : NSObject {
     AFJSONRequestOperation *operation;
}

//We'll get to know more of these later... These methods basically reports a success or fail back to the calling function right after the request has finished executing.
- (void)reportSuccess:(id)results; 
- (void)reportFailure:(NSError *)error;

//We'll also get to know more of these later. These methods will just set a completionBlock if the request reported a Success or failureBlock if the request reported a Failure.
- (void)setCompletionBlock:(ResultBlock)aCompletionBlock;
- (void)setFailedBlock:(FailedBlock)aFailedBlock;

- (void)start; //To Start the request
- (void)cancel; // To Cancel the request. This is important so we can stop the request whenever we want to.
- (BOOL)isExecuting; //To know if the request is being executed or is currently executing
- (BOOL)isFinished; //To know if the request has finished executing

//Optional
- (void)showSuccessAlertViewWithSuccessMessage:(NSString *)successMessage;
- (void)showFailedAlertViewWithErrorMessage:(NSString *)errorMessage;


@end

//RequestManager.m file
#import "RequestManager.h"
#import "APIClient.h"

@implementation RequestManager {
    ResultBlock completionBlock;
    FailedBlock failureBlock;
}

#pragma mark - Methods
- (void)reportSuccess:(id)results
{
    if (completionBlock) {
        dispatch_async(dispatch_get_main_queue(), ^{
            completionBlock(results);
        });
    }
}

- (void)reportFailure:(NSError *)error
{
    if (failureBlock) {
        dispatch_async(dispatch_get_main_queue(), ^{
            failureBlock(error);
        });
    }
}

#pragma mark - Block Setters
- (void)setCompletionBlock:(ResultBlock)aCompletionBlock
{
    completionBlock = [aCompletionBlock copy];
}

- (void)setFailedBlock:(FailedBlock)aFailedBlock
{
    failureBlock = [aFailedBlock copy];
}

#pragma mark - Public Methods

//Starts the operation.
- (void)start
{
    [[APIClient sharedClient] enqueueHTTPRequestOperation:operation];
}

//The operation has been canceled.
- (void)cancel
{
    [operation cancel];
}

//The operation is currently executing.
- (BOOL)isExecuting
{
    return [operation isExecuting];
}

// The operation has finished executing.
- (BOOL)isFinished
{
    return [operation isFinished];
}

#pragma mark - AlertViews
/**
 Shows a Successful Message Alert
 @param successMessage, a string with Success-related message
 */
- (void)showSuccessAlertViewWithSuccessMessage:(NSString *)successMessage
{
    UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"Success!" message:successMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alertError show];
}

/**
 Shows a Failed Message Alert
 @param errorMessage, a string with error message of what went wrong why the request failed
 */
- (void)showFailedAlertViewWithErrorMessage:(NSString *)errorMessage
{
    UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"Failed!" message:errorMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alertError show];
}


@end

5. The last thing that we are going to prepare is our ResultSet. What I mean by ResultSet is that what do we usually RECEIVE from the API? There are results that totally look the same, so we will just have to create an object of what the API usually returns.

So you have to decide what your API usually returns and just modify the list. You can always add or remove variables on the list -- make it as flexible as possible. A flexible and easy to reuse codes are GOOD codes ;-)

Currently, in my part, our API usually returns the following so this is how my ResultSet class currently look like:
#import <Foundation/Foundation.h>

@interface EventResultSet : NSObject

@property NSInteger currentPage;
@property NSInteger resultPageCount;
@property NSInteger resultCount;
@property (nonatomic, strong) NSString *noResultFoundText;
@property (nonatomicstrongNSMutableArray *results;


@end

#import "EventResultSet.h"

@implementation EventResultSet

//Just in case you still didn't know, in the new versions of Xcode, there was no need for us to synthesize the properties anymore. Ain't this nice?


@end

Let the fun of calling API services using blocks begin! :D

Can we first imagine this scenario in our app? Most social applications will need to fetch FEEDS from their server. "Feeds" are the status messages/updates of our "friends" or the people we "follow" and even ourselves. So imagine we have to fetch Feeds from our server...

You may opt to just create a Request class for all the API request functions that we will be making but I suggest we create request classes according to our modules for easier editing in the future.

6. Let's create a FeedRequest class which is a subclass of RequestManager (the one that we did above) for our Feeds module.

This is how my FeedRequest interface file looks like:
#import <Foundation/Foundation.h>
#import "RequestManager.h"

/**
 A customized request class for fetching feeds/feed-related fetches.
 */
@interface FeedRequest : RequestManager

//Methods
- (void)feedsWithParameters:(NSMutableDictionary *)parameters; //We will know later what the parameters will be.


@end

7. And in my FeedRequest implementation file, I will implement the feedsWithParameters: method.
/**
 Fetch list of events from the server. Increment currentPage in order to fetch more events.
 @param parameters, a dictionary of parameters that may include currentPage or page, and pageSize or limit of results.
 */
- (void)feedsWithParameters:(NSMutableDictionary *)parameters
{
    ResultSet *resultSet = [[ResultSet alloc] init];
    if (! resultSet.results) {
        resultSet.results = [[NSMutableArray allocinit];
    }

//Path is the string after our APIURL. From the API URL given above, if we get our list of feeds using the URL : https://jescdev.sample.com/api/feeds then our path value will be "feeds"
NSMutableURLRequest *request = [[GCAPIClient sharedClient] requestWithMethod:@"GET" path:@"feeds" parameters:parameters];
   
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
         //Assuming that the returned JSON looks like this. Do not include the texts on maroon to your code, this is just a sample response.
         JSON: {
        currentPage = 1;
        list =     (
                {
            status = Test
            userId = 1234;
            userName = JennCurativo;
            ...
         },
                {
            status = Hello
            userId = 1002;
            userName = Polka;
            ...
         },
               ...
                {
            status = Test 3
            userId = 5688;
            userName = Demitria;
            ...
         }
       );
      pageCount = 1;
      resultCount = 6;
      }

       //This is how we save the results:
        resultSet.currentPage = [JSON[@"currentPage"integerValue];
        resultSet.resultCount = [JSON[@"resultCount"integerValue];
        resultSet.resultPageCount = [JSON[@"pageCount"integerValue];
        NSArray *feeds = [JSON[@"list"allObjects];
        
        for (id result in feeds) {
            Feed *feed = [[Feed allocinit];
            //example on how to set feed values
            //feed.status = result[@"status"];
            [resultSet.results addObject:feed];
        }
        
        if ([feeds count] == 0) {
            resultSet.noResultFoundText = JSON[@"message"];
        }
        
        //Report success back to the calling function
        [self reportSuccessresultSet];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        //Report failure back to the calling function
        [self reportFailure:error];

        //Optional
        //[self showFailedAlertViewWithErrorMessage:error.localizedDescription];
    }];
}

Let's call this API method that we just created from our ViewController.

8. Hey, I'm assuming you have your view controllers ready. So what I'm gonna do is just simply make a function in calling our API method. :D

Inside our view controller .m file, we make a function:

- (void)fetchFeedsFromServer
{
        NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
        parameters[@"pageSize"] = [NSNumber numberWithInt:20];
        parameters[@"page"] = [NSNumber numberWithInteger:++self.currentPage];
        //Add any other parameters needed for your API request

        FeedRequest *request = [[FeedRequest allocinit];
        [request feedsWithParameters:parameters];
        [request setCompletionBlock:^(id results) {
                //Remember the [self reportSuccessresultSet]; code above (#7)? Upon reportingSuccess, it will go inside this block. resultSet is received by results
                //Processing of fetched data.
                ResultSet *resultSet = (ResultSet *)results;
                self.resultPageCount = resultSet.resultPageCount;
                self.currentPage = resultSet.currentPage;
            
                for (int i = 0; i < [resultSet.results count]; i++) {
                    [self.feedsArray addObject:[resultSet.results objectAtIndex:i]];
                }

                //Update our feeds tableview
               [self.tableView reloadData];
        }];
        [request setFailedBlock:^(NSError *error) {
                //If the request fails, the [self reportFailure:error]; will go inside this block.
        }];
        [request start];
}

There may be other BETTER way of doing this so if you have a better suggestion, feel free to comment below. I hope this somehow helps beginners in fetching data from the API.

Another blocks tutorial from one class to another - UP NEXT!

2 comments:

  1. Thank you, Jenn, your tutorial looks rather profound, congratulations! Read more here if you are interested in other tactics of blocking.

    ReplyDelete
  2. Hello Jann,

    Your tutorial is very helpful for me....May i get code for this tutorial?

    ReplyDelete