Move Web Service to Store


#1

I really enjoyed the book, and have a question about design best-practices.

In the web service example, all of the web service code is in the view controller.
It seems to make more sense to have this code in the Store class (or even a separate class used only by the store).
If the web service code is moved out of the view controller, what is the best practice for having the web service inform the view controller when the call has completed (so the tableView reload message can be sent)?

It seems the controller could pass a reference to itself when calling the store, allowing the store/web service to pass a message to the controller on completion.
Or the controller could maybe pass in it’s own block for the store/web service to run on completion.
Perhaps some other pattern is more widely used.

Please let me know what is the standard practice in such cases.

Thanks

Colin


#2

Hi there,

          they actually moved all web services to the store in the 3rd Edition of BNR iOS Programming. Not sure why they decided to omit this for the 4th edition. From my experiences, it really is a good practice to move all the web services to the store. Please find my sample code below.

I created BNRCoursesStore file. In the header file, I declared the fetchFeed method with a block variable as a parameter. The BNRCoursesViewController will send a block that it has created when it calls the fetchFeed method from the store.

In BNRCoursesStore.h

[code]#import <Foundation/Foundation.h>

@interface BNRCoursesStore : NSObject

// Notice that this is a class method and prefixed with a + instead of a -

  • (instancetype)sharedStore;

// Web Service Call

  • (void)fetchFeed:(void (^)(NSArray *, NSError *))block;

@end[/code]

In BNRCoursesStore.m

This is where the heavy work of the web service operation commences. When the web service returns with the JSON data, I parse it and feed the data back to the block that was sent by the BNRCoursesViewController as a parameter.

[code]//
// BNRCoursesStore.m
// Nerdfeed
//
// Created by robert on 12/6/14.
// Copyright © 2014 Big Nerd Ranch. All rights reserved.
//

#import “BNRCoursesStore.h”

@interface BNRCoursesStore()

@property (nonatomic) NSURLSession *session;

@end

@implementation BNRCoursesStore

  • (instancetype)sharedStore
    {
    // static variables are not stored in a method stack frame
    // Not destroyed when a method has completed execution
    static BNRCoursesStore *sharedStore = nil;

    // Do I need to create a sharedStore?
    // if(!sharedStore){
    // sharedStore = [[self alloc] initPrivate];
    // }

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    sharedStore = [[self alloc] initPrivate]; //run only once for thread safe
    });

    return sharedStore;
    }

// If a programmer calls [[BNRItemStore alloc]init], let him
// know the error of his ways

  • (instancetype)init
    {
    @throw [NSException exceptionWithName:@“Singleton” reason:@“Use +[BNRCoursesStore sharedStore]” userInfo:nil];

    return nil;
    }

// Here is the real (secret) initializer

  • (instancetype)initPrivate
    {
    self = [super init];

    return self;
    }

  • (void)fetchFeed:(void (^)(NSArray *, NSError ))block
    {
    NSURLSessionConfiguration config = [NSURLSessionConfiguration defaultSessionConfiguration];
    /
    _session = [NSURLSession sessionWithConfiguration:config
    delegate:nil
    delegateQueue:nil];
    /

    _session = [NSURLSession sessionWithConfiguration:config
    delegate:self
    delegateQueue:nil];

    NSString *requestString = @“https://bookapi.bignerdranch.com/private/courses.json”;
    NSURL *url = [NSURL URLWithString:requestString];
    NSURLRequest *req = [NSURLRequest requestWithURL:url];

    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
    // NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    // NSLog(@"%@", json);
    NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    NSArray *courses = jsonObject[@“courses”];
    block(courses, nil);
    }];

    [dataTask resume];
    }

// Will keep calling this if authentication fails
// Solution: Check previousFailureCount for number of unsuccessful logins

  • (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
    {
    if ([challenge previousFailureCount] == 0) {
    NSURLCredential *cred = [NSURLCredential credentialWithUser:@"BigNerdRanch"
    password:@"AchieveNerdvana"
    persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
    }
    else{
    NSLog(@“Failed”);
    }

}

@end
[/code]

In BNRCoursesViewController.m, I created a callback block in the fetchFeed method. This is the callback block that will be called by the BNRCoursesStore when it returns with the JSON data.

[code]- (void)fetchFeed
{
//Beginning block definition
void(^completionBlock)(NSArray *obj, NSError *err) =
^(NSArray *obj, NSError *err)
{
self.courses = obj;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});

};//end of block definition

[[BNRCoursesStore sharedStore]fetchFeed:completionBlock];

}[/code]

So how this works is that the completionBlock variable in BNRCoursesViewController will be called by the BNRCourseStore once the web service has returned with a data object from the JSON, and the update can be done within the view controller respectively. You still need to grab the main thread though, as the NSURLSession is still running on a background thread even though it has called the completionBlock variable.

Hope this helps!!! :wink: