One really useful feature of Objective-C is the ability to extend an existing class without the need to sub-class via inheritance. This means that existing code can stay in place whilst additional functionality is added for the convenience of future maintenance.
Categories is the feature that makes this possible. In effect, this says to the compiler, ‘add on these methods to the existing class’. One place I’ve found them useful is in convenience methods on NSFetchedResultsController
. Getting row counts for sections, or objects from sections is non-intuitive due to a lack of a couple of methods. You have to go via the sections
array, even when you are not using sections in the fetch, and you need to build an NSIndexPath
to extract an object, again, even if sections are not relevant.
Defining the Categories
To add a category to an existing class, we use the @interface
definition again, but this time put the Category name in brackets, in a similar manner to adopting a protocol. RSFetchedResultsControllerHelpers
is the category name, in this case ‘RS’ is an acronym of my company name to keep things from classing with other potential names, and ‘Helpers’ is a nice reminder of why I’m adding them.
Here we are defining two instance methods that create a shortcut to getting row counts for a section, and an object from an indexPath without the need to create an instance of indexPath directly.
@interface NSFetchedResultsController (RSFetchedResultsControllerHelpers)
-(NSUInteger) numberOfObjectsForSection: (NSUInteger) section;
-(id) objectAtRow: (NSUInteger) row inSection: (NSUInteger) section;
@end
The above goes into a separate .h file. There is a convention for the naming of Category headers and implementation files, which is to name the file after the original class name, then ‘+’ followed by the Category name. So save that above to NSFetchedResultsController+RSFetchedResultsControllerHelpers.h
.
Implementing the Categories
We now create the implementations of the Category methods, and place the following in NSFetchedResultsController+RSFetchedResultsControllerHelpers.m
.
@implementation NSFetchedResultsController (RSFetchedResultsControllerHelpers)
-(NSUInteger) numberOfObjectsForSection: (NSUInteger) section {
id <NSFetchedResultsSectionInfo> sectionInfo;
sectionInfo = [self.sections objectAtIndex: section];
return sectionInfo.numberOfObjects;
}
-(id) objectAtRow: (NSUInteger) row inSection: (NSUInteger) section {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
return [self objectAtIndexPath: indexPath];
}
@end
In numberOfObjectsForSection
we are accessing the NSFetchedResultsSectionInfo
information from the sections
collection for the appropriate section, and then returning the result.
In objectAtRow:InSection
we are constructing the NSIndexPath
instance within the function, which helps to keep the callers code cleaner and less cluttered. Note that there is no need to call [cci]release[/cci] on the NSIndexPath
instance as its created via a convenience constructor (that has already autoreleased the instance).
Making Use of the Category Methods
The following code snippet shows how we can now make use of these methods, in this case within the UITableViewDataSource
protocol methods of a Table View.
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
return [self.fetchedResultsController numberOfObjectsForSection: 0];
}
-(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self.fetchedResultsController objectAtRow:section inSection:0] title];
}
-(NSInteger) tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
Group *group =[self.fetchedResultsController objectAtRow:section inSection:0];
return [group.datasets count];
}