July 2011

How To Use Custom Classes With Core Data Without Fear

Filed in iDevBlogADay, iOS Development | Comments (0) |

If you are using CoreData in your iOS app, you have several options for your domain objects. Here is a short list of ways you could use Core Data objects in your app:

  1. Use generic NSManagedObject, no custom classes
  2. Create custom classes by hand
  3. Create a file template to use when creating custom classes
  4. Use an external tool to create custom classes
  5. Let Xcode generated custom classes, then modify them
  6. Add categories to Xcode generated custom classes

My general approach to development is to let Xcode do the tedious bits for me, so my approach to Core Data objects is #6. Let’s take a brief look at the other options first.

1. Simple Approach: No Custom Classes

Pick up almost any book that covers CoreData, and you will probably find the introductory example does not use custom classes at all. That is clearly the proper place to begin when teaching CoreData — you want to introduce topics in manageable chunks when someone is first learning a subject.

If you create a new project using Navigation-based Application template in Xcode, and select the “Use Core Data” checkbox, the initial project will create a simple model with one Entity, ‘Event’ which does not have a custom class.

DemoCustomCoreDataClasses 001

If your needs are simple, this method may be enough — it is certainly enough to play around with Core Data and learn what is going on. And for simple applications, you can manage the data just fine using the generic NSManagedObject instances . But if you need app-specific domain objects, then you will need to create custom classes for each of your domain objects.

Xcode lets you specify a custom class name on the property sheet for the entity, so let’s look at some ways to use that.

#2 Create custom classes by hand

I have never created a custom class by hand. There is no special magic to it, just some tedious work to make sure you correctly cover all the behavior needed. There can be value in doing things by hand once just to see what is involved in the process, but it is clearly not a useful long-term approach.

#3 Create a file template to use when creating custom classes

If you really have a desire to have non-standard, hand-crafted custom classes, then once you got it as you wanted, you could create a file template for it. I don’t recommend this any more than method #2, other options have the same flexibility with less work.

#4 Use an external tool to create custom classes

Some of the standard tools in many experienced Core Data developer’s toolbox have been Jonathan ‘Wolf’ Rentzsch’s tools: mogenerator and Xmo’d.

mogenerator is a program that builds two custom classes for each entity in a Core Data model: a machine version and a human version.

The machine version handles the basic behavior you need from a custom Core Data class (like Xcode’s generated classes). It is expected that the machine version will get overwritten every time the model changes, so you should not make any changes here — they will be lost.

The human version extends the machine version and is the class you modify for your own app specific behavior.

Xmo’d is a plugin for Xcode 3 that automatically detected changes to the model and regenerated classes as appropriate. Unfortunately, Apple changed something in AppleScript in Xcode 4 and broke something that the Xmo’d plugin needed.

mogenerator works just fine still from the command line, so is still a good choice. There are custom templates based on Jonathan’s work that you can use or modify as well, but you will need to get the project from github to see them.

#5 Let Xcode generated custom classes, then modify them

Xcode will generate custom classes for you based on the entity definition in the model. I like having Xcode generate classes instead of a third party tool so I do not have to worry (as much) about things breaking when new versions of Xcode are released, or missing things that might be part of an updated Core Data framework.

Just as with mogenerator, you can run regenerate these classes any time your model changes, but it will also completely overwrite the existing classes. So, if you choose to modify the generated classes directly, you are forced to do redo your custom work each time — not really a good choice.

Fortunately, there is a straightforward way to avoid this issue.

#6 Add categories to Xcode generated custom classes

Xcode generated custom classes + categories on those classes is a great combination, one that I have used that last several times I have needed Core Data support in an app. It has the benefits of mogenerator, plus the advantage of being a pure Xcode approach.

First, given this simple Core Data model with two entities, Division and Department:

DemoCustomCoreDataClasses 002

If we assign a Class to each of them, like ‘MyDivision’ and ‘MyDepartment’, then we can ask Xcode to generate the classes by selecting both entities in the Core Data modeler view, right click on the appropriate folder in the project view, and select ‘New File’ from the menu:

DemoCustomCoreDataClasses 003

Choose NSManagedObject subclass from the ‘Core Data’ group:

DemoCustomCoreDataClasses 004

And Xcode will generate classes like this:

//  MyDivision.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class MyDepartment;

@interface MyDivision : NSManagedObject {
@private
}
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSSet* division;

@end
//  MyDivision.m

#import "MyDivision.h"
#import "MyDepartment.h"


@implementation MyDivision
@dynamic name;
@dynamic division;

- (void)addDivisionObject:(MyDepartment *)value {    
    NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    [[self primitiveValueForKey:@"division"] addObject:value];
    [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    [changedObjects release];
}

- (void)removeDivisionObject:(MyDepartment *)value {
    NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    [[self primitiveValueForKey:@"division"] removeObject:value];
    [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    [changedObjects release];
}

- (void)addDivision:(NSSet *)value {    
    [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
    [[self primitiveValueForKey:@"division"] unionSet:value];
    [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
}

- (void)removeDivision:(NSSet *)value {
    [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
    [[self primitiveValueForKey:@"division"] minusSet:value];
    [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
}


@end

We can now create some category classes to add our own custom behavior and expose the add and remove division methods that are part of the generated implementation class.

//  MyDivision+helper.h

#import <Foundation/Foundation.h>

@protocol MyDivisionMethods <NSObject>
@optional
- (void)addDivisionObject:(MyDepartment *)value;
- (void)removeDivisionObject:(MyDepartment *)value;
@end

@interface MyDivision(helper)<MyDivisionMethods>

@property (nonatomic,readonly) BOOL valid;

- (void) myCustomMethod;
- (BOOL) validate:(NSMutableArray *) errors;

@end
//  MyDivision+helper.m

#import "MyDivision.h"
#import "MyDivision+helper.h"

@implementation MyDivision (helper)

- (void) myCustomMethod {
// do something here
}

- (BOOL) valid {
    return [self validate:nil];
}

- (BOOL) validate:(NSMutableArray *) errors {
    BOOL result = YES;

// check object state and children for validity    
// add error messages to errors array when problems are found

    return result;
}

@end

As long as you put all your custom behavior in the MyDivision(helper) or MyDepartment(helper) categories, and not in the MyDivision or MyDepartment class, you can have Xcode recreate the MyDivision and MyDepartment classes without worry.

Remember, to make use of the methods implemented or exposed in your category, import both header files in the classes that need that behavior:

#import "MyDivision.h"
#import "MyDivision+helper.h"

...

Conclusion

There are many useful bits of Core Data advice out on the web, but this is definitely one area where a good book can help.

There are a number of books out there, many of them good, but since Core Data and Xcode keep changing, you want to find a fairly recent edition of one. For iOS apps, I recommend “Core Data for iOS” by Tim Isted and Tom Harrington.

I am leading a half-day session this September at 360iDev on using OCMock in your automated tests (you are doing automated testing, right?) You should come join us, 360iDev is always a great time for so many reasons.

Also, I am doing two sessions at a new conference, CocoaConf, in Columbus Ohio on August 12th & 13th. It is a new conference, so I can’t tell you about past performance, but it has a great set of featured speakers, and I fully expect it to be a great time. Check it out!

Have a great day!


This is post 7 of 10 on my second iDevBlogADay run.

We all need the support of others to do our best work. Find other like-minded developers that will provide encouragement and motivation through local user groups, conferences or meetups. A great collection of indie iOS developers have helped me stay on track through meetups, 360iDev, twitter, and iDevBlogADay.

I regularly attend Cocoa/iPhone developer meetups in Cincinnati, Ohio and Columbus, Ohio. If you are in the central or southwest Ohio area, come join me at either monthly meetup, or join us in Columbus for CocoaConf in August:

If you depend on iOS development for your livelihood, or would like to get to that point — you really need to attend a conference dedicated to helping you get better, and I can think of no better conference for that purpose than 360iDev — you should register today!. Much of what I am able to do professionally is due to the things I learned and the people I met there.

Finally, here is a little more information about me, Doug Sjoquist, and how I came to my current place in life. You should follow me on twitter and subscribe to my blog. Have a great day!

Don’t Nest Those UIViewControllers!

Filed in iDevBlogADay, iOS Development | Comments (0) |

Like many iOS developers, I came to iOS from a non-Mac development background. Switching platforms always requires some time to adjust to the different libraries and usage patterns of the new platform, and Cocoa is no exception. I really like it now, and am very comfortable working with it, but there were some things it took a little while for me to use properly.

Nasty, Nested UIViewControllers

UIViewControllers and related classes are straightforward and pretty clear in their purpose. Apple’s user guide gives a good overview and is worth reviewing from time to time. But I have to admit that early on, I was consistently misusing custom UIViewControllers by trying to nest one within the other.

Like many beginners, my approach was wrong, but I managed to make it “work” anyway. I would usually create a new custom UIViewController subclass that managed a view and hacked together working behavior through liberal use of viewDidLoad and other methods like that. Nothing I’m proud of, or that I want to share with you — but I know I’m not alone, so I don’t feel too bad about it.

In principle what I wanted was a reusable view with a corresponding controller just for that view, my mistake was in assuming that the controller had to be an instance of UIViewController. Apple is quite clear in their documentation that there should be a single UIViewController that manages the overall view. Even as I twisted multiple, nested UIViewControllers to do my will, I knew it was a bad idea, but early on I was not sure how to do it better.

Simple Answers Are Sometimes Hard To See

When your mind is following wrong assumptions and thought processes, the truth can be hard to see.

The answer was actually quite simple. I can do exactly what I want with just a simple class for the view’s controller. I can even use Interface Builder if I choose to design the view and configure outlets and actions.

Part of my early error was because I wanted the iOS runtime to somehow magically know about my views and send them the same set of viewDidLoad, viewWillAppear, and related messages that UIViewControllers receive when used properly. I realized that trying to twist multiple UIViewController instances into behaving like the magic existed was foolish, so I found a better way.

New Pattern For Reusable Subviews

My current pattern is a simple approach that is an amalgam of various methods I learned from others. I use a single UIViewController to manage the overall view, and replace portions of the subview hierarchy as needed with individual views that have their own controller, which I call a view manager to avoid confusion. (After all, naming things is purported to be one of the two truly hard things in computer science.)

I have a standard set of ViewManagers that I use across projects, which derive from the same AbstractViewManager class and include a few standard behaviors. Each ViewManager instance is owned by a “real” UIViewController instance, and to help ViewManagers participate more fully in life cycle and memory events of UIViewControllers, the owning UIViewController instance forwards messages to its ViewManager instances as appropriate.

The accompanying example project has a split view controller at the root, with a list of sample data to display in the master table view. The simple detail is handled by DetailViewController which owns an instance of two ViewManagers: WebViewManager and ImageViewManager.

When a row is selected, the master table view controller sets the ‘detailItem’ property of DetailViewController, which expects a dictionary of values. The setter method for the detail item looks like this:

- (void)setDetailItem:(id)newDetailItem {
    [detailItem_ autorelease];
    detailItem_ = [newDetailItem retain];

    if (self.popoverController != nil) {
        [self.popoverController dismissPopoverAnimated:YES];
    }        

    NSString *detailType = [detailItem_ valueForKey:@"type"];
    if ([detailType isEqualToString:@"image"]) {
        self.currentViewManager = self.imageViewManager;
        [self.imageViewManager loadImage:[detailItem_ valueForKey:@"name"]
                                   title:[detailItem_ valueForKey:@"title"]];
    } else if ([detailType isEqualToString:@"web"]) {
        self.currentViewManager = self.webViewManager;
        [self.webViewManager loadURL:[detailItem_ valueForKey:@"url"]
                               title:[detailItem_ valueForKey:@"title"]];
    } else {
        self.currentViewManager = nil;
    }

}

This will set the currentViewManager property to the appropriate ViewManager instance and configure its view appropriately.

The custom setter for currentViewManager sends the appropriate life cycle events to both the old and new ViewManager:

- (void) setCurrentViewManager:(AbstractViewManager *)currentViewManager {
    if (currentViewManager == currentViewManager_) {
        return;
    }

    AbstractViewManager *oldViewManager = [currentViewManager_ autorelease];
    NSArray *subviews = [self.contentView subviews];
    
    if (oldViewManager) {
        [oldViewManager viewWillDisappear:YES];
    }
    for (id subview in subviews) {
        [subview removeFromSuperview];
    }
    if (oldViewManager) {
        [oldViewManager viewDidDisappear:YES];
    }
    
    currentViewManager.view.frame = self.contentView.bounds;
    if (currentViewManager) {
        [currentViewManager viewWillAppear:YES];
    }
    
    [self.contentView addSubview:currentViewManager.view];
    currentViewManager_ = [currentViewManager retain];    
    
    if (currentViewManager_) {
        [currentViewManager_ viewDidAppear:YES];
    }
}

Each ViewManager instance is lazy loaded, and unloads itself when the property is reset like this:

- (WebViewManager *) webViewManager {
    if (webViewManager_ == nil) {
        webViewManager_ = [[WebViewManager alloc] initWithNibName:@"WebViewManager" bundle:nil options:nil];
        [webViewManager_ viewDidLoad];
    }
    return webViewManager_;
}

- (void) setWebViewManager:(WebViewManager *) webViewManager {
    if (webViewManager_ == webViewManager) {
        return;
    }
    
    [webViewManager_ viewDidUnload];
    [webViewManager_ autorelease];
    webViewManager_ = [webViewManager retain];
}

DetailViewController also forwards ‘didReceiveMemoryWarning:’, ‘viewDidUnload:’, and other messages to instantiated ViewManagers when needed.

Conclusion

The ImageViewManager and WebViewManager classes are just simple examples of what can be done, but they should point you in the right direction.

I hope this will help someone else dig themselves out the nested UIViewController rathole that I fell into on my iOS development journey. If this sounds useful to you, download the demo project and give it a try.

Have a great day!


This is post 6 of 10 on my second iDevBlogADay run.

We all need the support of others to do our best work. Find other like-minded developers that will provide encouragement and motivation through local user groups, conferences or meetups. A great collection of indie iOS developers have helped me stay on track through meetups, 360iDev, twitter, and iDevBlogADay.

I regularly attend Cocoa/iPhone developer meetups in Cincinnati, Ohio and Columbus, Ohio. If you are in the central or southwest Ohio area, come join me at either monthly meetup:

If you depend on iOS development for your livelihood, or would like to get to that point — you really need to attend a conference dedicated to helping you get better, and I can think of no better conference for that purpose than 360iDev — you should register today!. Much of what I am able to do professionally is due to the things I learned and the people I met there.

Finally, here is a little more information about me, Doug Sjoquist, and how I came to my current place in life. You should follow me on twitter and subscribe to my blog. Have a great day!