HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Ensuring non-expired token before every request

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
nonexpiredrequesteverytokenbeforeensuring

Problem

I'm developing an iOS application where data is fetched from a third-party REST API. Each request must contain an authorization token.

In order to implement this, I've written a fetchToken withCompletionBlock:(void (^)(ASToken) token,NSError error) method in AppDelegate:

-(void) fetchTokenWithCompletionBlock:(void (^)(ASToken * token, NSError * error)) completionBlock
{
    if (!_token) {
        _token = [self retrieveTokenFromKeychain];
    }

    if (!_token || _token.isExpired) {
        [ASMyApi loadTokenWithcompletionBlock:^(ASToken *token, NSError*  error) {

            if (token) {
                _token = token;
                [self saveTokenToKeychain: token];
            }else{
                completionBlock(nil,&error);
            }

        }];
    }else{
        completionBlock(_token,nil);
    }
}


Before every requests that requires Authorization, fetchToken withCompletionBlock:(void (^)(ASToken*) token,NSError ** error) is called to retrieve the token. Here's an example:

-(IBAction) loadCategories
{
    [self.refreshControl beginRefreshing];
    [((AppDelegate *)[UIApplication sharedApplication].delegate) fetchTokenWithCompletionBlock:^(ASToken *token, NSError *error) {

        if (token) {
            [ASMyApi loadCategories:token.token completionBlock:^(NSArray *categories, NSError *error) {

                [self.refreshControl endRefreshing];

                if (categories){

                    self.categories = categories;

                }else{

                    [self monitorNetworkReachability];

                }

                [self updateUI];

            }];

        }else{

            [self.refreshControl endRefreshing];
            [self monitorNetworkReachability];
        }

    }];
}


I'm not happy with this code for the following reasons:

-
The nested nature of the callbacks makes it look ugly and somewhat unreadable

-
Too much repetition: The code to get the token is verbose and must be

Solution

You could introduce a delegate instead passing another block in loadCategories method.

First, the protocol:

@protocol ASMyApiCategoriesDelegate 

    @required
    - (void) ASMyApiCategoriesDidLoad:(NSArray *)categories;
    - (void) ASMyApiCategoriesDidFailToLoadWithError:(NSError *)error

@end


You change the signature of + [ASMyApi loadCategories:completionBlock:] to accept a delegate:

+ (void) loadCategoriesToken:(id)token delegate:(id )delegate
{
    NSArray *categories;
    // code that loads categories

    if (categories)
    {
        [delegate ASMyApiCategoriesDidLoad:categories];
    }
    else
    {
        [delegate ASMyApiCategoriesDidFailToLoadWithError:nil];
    }
}


In your view controller you'll have to implement the required methods from ASMyApiCategoriesDelegate protocol and update loadCategories method.

- (void) ASMyApiCategoriesDelegateDidLoad:(NSArray *)categories
{
    [self.refreshControl endRefreshing];
    self.categories = categories;
    [self updateUI];
}

- (void) ASMyApiCategoriesDelegateDidFailToLoadWithError:(NSError *)error
{
    [self.refreshControl endRefreshing];
    [self monitorNetworkReachability];
}

- (IBAction) loadCategories
{
    [self.refreshControl beginRefreshing];
    [((AppDelegate *)[UIApplication sharedApplication].delegate) fetchTokenWithCompletionBlock:^(ASToken *token, NSError *error) {
        if (token)
        {
            [ASMyApi loadCategories:token.token delegate:self];
        }
        else
        {
            [self ASMyApiCategoriesDidFailToLoadWithError:nil];
        }
    }];
}

Code Snippets

@protocol ASMyApiCategoriesDelegate <NSObject>

    @required
    - (void) ASMyApiCategoriesDidLoad:(NSArray *)categories;
    - (void) ASMyApiCategoriesDidFailToLoadWithError:(NSError *)error

@end
+ (void) loadCategoriesToken:(id)token delegate:(id <ASMyApiCategoriesDelegate>)delegate
{
    NSArray *categories;
    // code that loads categories

    if (categories)
    {
        [delegate ASMyApiCategoriesDidLoad:categories];
    }
    else
    {
        [delegate ASMyApiCategoriesDidFailToLoadWithError:nil];
    }
}
- (void) ASMyApiCategoriesDelegateDidLoad:(NSArray *)categories
{
    [self.refreshControl endRefreshing];
    self.categories = categories;
    [self updateUI];
}

- (void) ASMyApiCategoriesDelegateDidFailToLoadWithError:(NSError *)error
{
    [self.refreshControl endRefreshing];
    [self monitorNetworkReachability];
}

- (IBAction) loadCategories
{
    [self.refreshControl beginRefreshing];
    [((AppDelegate *)[UIApplication sharedApplication].delegate) fetchTokenWithCompletionBlock:^(ASToken *token, NSError *error) {
        if (token)
        {
            [ASMyApi loadCategories:token.token delegate:self];
        }
        else
        {
            [self ASMyApiCategoriesDidFailToLoadWithError:nil];
        }
    }];
}

Context

StackExchange Code Review Q#111405, answer score: 6

Revisions (0)

No revisions yet.