An Introduction to Categories (Part 2 of 2)

    February 24th, 2011 Posted by: - posted under:Featured » Tutorials

    Using Categories to enhance models, and get rid of those pesky compiler warnings

    Overview
    When using Core Data, our model classes are always generated. What happens if we wanted to add a couple utility functions to one of these generated classes? Yep, they would be discarded the next time we auto-generated our model classes. As we discussed in our previous categories post (icodeblog.com), adding a category on one of these generated classes would enable us to add those utility functions in without them being erased when we generate the core data models.

    For example:
    Say we created a Model object called Person, and added two NSString attributes called firstName and lastName. If we had populated our Core Data base with several Person objects, and then went to retrieve them, we would have an unsorted array. What if we wanted them to be sorted based on their names? One of the ways to accomplish this could be to implement a function called compareByName: that will return an NSOrderedResult. We can use this utility function to sort that array of Person’s.

    Given a Core Data generated class from a model object called Person, we are given a file and it’s header of the form

    //Person.h
    #import
    @interface Person : NSManagedObject
    {
    }
     
    @property (nonatomic, retain) NSString *firstName;
    @property (nonatomic, retain) NSString *lastName;
     
    @end
    ------------------------------------------------------------------------
    //Person.m
    #import "Person.h"
     
    @implementation Person
     
    @dynamic firstName
    @dynamic lastName
     
    @end

    To add our category, we create an NSObject file of the name Person+Sorting.m, making sure that you check the create header file option.
    As in the previous tutorial, change the files to actually be a category, and add in our new sorting function.

    //Person+Sorting.h
    @interface Person (Sorting) 
     
    - (NSOrderedResult)compareByName:(Person *)person2;
     
    @end
    ----------------------------------------------------------------------------------------
    //Person+Utility.m
    @implementation Person (Sorting)
     
    // Returns an NSComparisonResult caseInsensitiveCompare by lastName, and if that is the same, then by firstName
    - (NSComparisonResult)compareByName:(Person *)person2 {
            NSComparisonResult result = [self.lastName caseInsensitiveCompare:person2.lastName];
            if (result == NSOrderedSame) {
                    return [self.firstName caseInsensitiveCompare:person2.firstName];
            }
            return result;
    }
    @end

    As you can see, I added in some code that first compare’s the Person objects based on their last name, and if those are equal compares them on their first names.

    Now to use this category, all we have to do is add

    #import "Person+Sorting.h"

    to any file where we use Person, and we will be able to use that compareByName: function.

    NSError *error = nil;
     
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:managedObjectContext];
    [request setEntity:entity];
     
    NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];
     
    //Turn those unsorted results into a sorted array
    NSArray *sortedPersons = [results sortedArrayUsingSelector:@selector(compareByName:)];
     
    [request release];

    There are of course other ways to get back sorted results from core data, but this is just one of the ways.

    Getting rid of some compiler warnings
    Another use of categories is to get rid of those pesky compiler warnings, when you call a function that is in the same file, however it is just lower down in the file.
    The following code does not cause a compiler warning because the function we are calling comes first:

    @implementation PersonViewController
     
    - (void)loadPersonObjects {
            NSLog(@"function that comes before.");
    }
     
    - (void)viewDidLoad {
            [super viewDidLoad];
            [self loadPersonObjects];
    }
    @end

    However if for some reason, like you want certain functions clumped together at one spot in a file, and you have viewDidLoad at the beginning, then something like the following would give a compiler warning.

    @implementation PersonViewController
    #pragma mark -
    #pragma mark View lifecycle
    - (void)viewDidLoad {
            [super viewDidLoad];
            [self loadPersonObjects];
    }
     
    #pragma mark -
    #pragma mark Load from Core Data Functions
    //Other functions that load from core data also....
    //.....
    //.....
    - (void)loadPersonObjects {
            NSLog(@"function that comes after.");
    }
    @end

    To get rid of that compiler warning you can create a category on the current class in which you declare all the functions that are giving you warnings. You can do this at the beginning of the .m file after all the #imports, and before the actual @implementation of your class. The reason to do this is because you don’t want other classes calling these private classes, so you don’t want them in the header file, but you do want to get rid of the warnings. The following code snippet shows an example of fixing the warnings in the above class.

    @implementation PersonViewController (__PRIVATE__)
    - (void)loadPersonObjects;
    @end
     
    @implementation PersonViewController
    #pragma mark -
    #pragma mark View lifecycle
    - (void)viewDidLoad {
            [super viewDidLoad];
            [self loadPersonObjects];
    }
     
    #pragma mark -
    #pragma mark Load from Core Data Functions
    //Other functions that load from core data also....
    //.....
    //.....
    - (void)loadPersonObjects {
            NSLog(@"function that comes after.");
    }
    @end