Wednesday, March 24, 2010

Refactoring Suggested Items: The template

The container template


The first template is a small set of code that has some utility routines to create two types of tables, and which contains the other strategies.  It is currently a little clunky, a future refactoring will try to make the API cleaner


The header file



/
//  JLTableContainer.h
//  JLFoundation
//
//  Created by Jon Lundy on 3/20/10.
//

#import
#import

@interface JLTableContainer : NSObject
{
NSObject<JLCallbackHandler> *callbackHandler;
NSObject<JLTableSource> *tableSource;
NSObject<JLCellProvider> *cellProvider;
NSObject<JLTableController> *tableController;
UITableView *table;
}
@property (retain,nonatomic) NSObject *callbackHandler;
@property (retain,nonatomic) NSObject *tableSource;
@property (retain,nonatomic) NSObject *cellProvider;
@property (retain,nonatomic) NSObject *tableController;
@property (retain,nonatomic) UITableView *table;

// Create a set of controllers for a simple fetch controller that
// sorts on a given sort with a key displayed for the value of hat field.
// the tableController is passed in.
+ (JLTableContainer*) createFetchControlledTable:(UITableView *)table
forEntity:(NSString *)entityName forSimpleKey:(NSString *)keyName
inContext:(NSManagedObjectContext *) context
createSectionsBy:(NSString *) sectionName
controlledBy:(NSObject<JLTableController>*) controller;
+ (JLTableContainer*) createFetchControlledTable:(UITableView *)table
  forEntity:(NSString *)entityName forSimpleKey:(NSString *)keyName
  inContext:(NSManagedObjectContext *) context
createSectionsBy:(NSString *) sectionName
controlledBy:(NSObject<JLTableController>*) controller
accessoryDisplay:(UITableViewCellAccessoryType) accessoryType;

// If this source supports searching via a predicate, then update the
// predicate with that call.  Note that if the source DOES NOT
// restrict by predicate, then this call will do nothing.

- (void) updatePredicate:(NSPredicate *) predicate;
// Connect the objects to each other to form the delegation chain of the
// template object.
- (void) connectObjects;
@end




The source file



//
//  JLTableContainer.m
//  JLFoundation
//
//  Created by Jon Lundy on 3/20/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "JLTableContainer.h"


@implementation JLTableContainer

@synthesize callbackHandler;
@synthesize tableSource;
@synthesize cellProvider;
@synthesize tableController;
@synthesize table;
+ (JLTableContainer*) createFetchControlledTable:(UITableView *)table
  forEntity:(NSString *)entityName forSimpleKey:(NSString *)keyName
  inContext:(NSManagedObjectContext *) context
createSectionsBy:(NSString *) sectionName
controlledBy:(NSObject<JLTableController>*) controller
{
return [JLTableContainer createFetchControlledTable:table forEntity:entityName forSimpleKey:keyName inContext:context 
createSectionsBy:sectionName controlledBy:controller
accessoryDisplay:UITableViewCellAccessoryNone];
}


+ (JLTableContainer*) createFetchControlledTable:(UITableView *)table
  forEntity:(NSString *)entityName forSimpleKey:(NSString *)keyName
  inContext:(NSManagedObjectContext *) context
createSectionsBy:(NSString *) sectionName
controlledBy:(NSObject<JLTableController>*) controller
accessoryDisplay:(UITableViewCellAccessoryType) accessoryType

{
JLTableContainer *returnValue=[[JLTableContainer alloc]init];
SimpleDescriptionCellProvider *simpleCellProvider=[[SimpleDescriptionCellProvider alloc] init];
simpleCellProvider.accessoryType=accessoryType;
returnValue.table=table;
JLFetchedControllerSource *source=[[JLFetchedControllerSource alloc] init];
source.sortField=keyName;
source.managedEntityName=entityName;
source.sectionField=sectionName;
source.fetchContext=context;
JLStandardCallbackHandler *callHandler=[[JLStandardCallbackHandler alloc]init];
returnValue.tableController=controller;
returnValue.cellProvider=simpleCellProvider;
returnValue.tableSource=source;
returnValue.callbackHandler=callHandler;
[returnValue connectObjects];
return [returnValue autorelease];
}

- (void) connectObjects
{
table.delegate=callbackHandler;
table.dataSource=tableSource;
if (callbackHandler!=nil)
{
callbackHandler.tableController=tableController;
callbackHandler.source=tableSource;
}
if (tableSource!=nil)
{
tableSource.cellProvider=cellProvider;
tableSource.table=table;
}
if (cellProvider!=nil)
{
}
if (tableController!=nil)
{

}
}

- (void) updatePredicate:(NSPredicate *) predicate
{
// Check to make source our source is not null and implements the
// requested protocol.
if (self.tableSource!=nil)
{
if (  [tableSource conformsToProtocol:@protocol(RestrictWithPredicate)]  ) {
id<RestrictWithPredicate> restricted=(id<RestrictWithPredicate>) self.tableSource;
[restricted updatePredicate:predicate];
}
}
}
- (void) dealloc
{
[callbackHandler release];
[tableSource release];
[cellProvider release];
[tableController release];
[super dealloc];
}

@end

Points of Interest

Static builder methods

The first thing to note is that there is a rather large public static method that lets you construct a fetched result controller which has standard behavior.  This API is clumsy, but it should work.  
In addition I defined an updatePredicate utility method. This method will first verify that the table source
corresponds to the new protocol, RestrictWithPredicate.  If it does, then it will update the predicate with
the passed in data.  This allows me to have various optional functions that can be used by manipulating the container object instead of having a lot of casting in the middle.

My goal is to restructure this API a little bit so that it is cleaner, and more modular to use.
if (self.tableSource!=nil)
{
if (  [tableSource conformsToProtocol:@protocol(RestrictWithPredicate)]  ) {
id<RestrictWithPredicate> restricted=(id<RestrictWithPredicate>) self.tableSource;
[restricted updatePredicate:predicate];
}
}

Restructuring calls


The method connectObjects is used to restructure the various strategies.  Each strategy knows about other strategies in the suite.  This call enables you to change strategies, and then make a single call to connect everything back up again.

Usage


Now that I have these methods in place I was able to quickly restructure my main sight word list to instead of selecting all sight words into memory, using a NSFetchedResultsController.  I disconnected the .nib file from the File Owner in Interface Builder, and added the following code in viewDidLoad.  

self.container=[JLTableContainer createFetchControlledTable:self.tableView
  forEntity:@"WordList"
  forSimpleKey:@"ListName"
  inContext:currentState.control .managedObjectContext
  createSectionsBy:@"Category" 
  controlledBy:self
  accessoryDisplay:UITableViewCellAccessoryDetailDisclosureButton
];
[self.container updatePredicate:nil];

I then removed all of the standard table callbacks, and implemented these two callbacks:

Selecting a row

- (void) rowSelected:(id) selectedObject
{
WordList *list=(WordList*)selectedObject;
SightWordDisplay *display=[[SightWordDisplay alloc] initWithNibName:@"SightWordDisplay" bundle:nil];


SightWordProvider *provider=[[SightWordProvider alloc] initWords:list];
display.provider = provider;
// display.title=provider.wordList.ListName;
// Copied from Beginning IPhone Development. Not sure I like the global reference.
[self.navigationController  pushViewController:display animated:YES];
[display release];
}

Selecting an accessory



- (void) acessorySelected:(id) selectedObject
{
ListEditor *editor=[[ListEditor alloc ] initWithNibName:@"ListEditor" bundle:nil];
editor.wordlist=(WordList*)selectedObject;
SightWordsUnlimitedAppDelegate *delegate =
[[UIApplication sharedApplication] delegate];

[delegate.rootController pushViewController:editor animated:YES];
[editor.tableView reloadData];
[editor release];

}

The goal is to remove the table management logic from my view controller, and let it concentrate on business logic.

Note that I broke the ability to delete word lists when I did this.  I'm going to have to go put it back in.  The nice thing about a shared class is that when I add this functionality back in, it will be available to any class using the library.



No comments:

Post a Comment