Reading these, I thought about my own solution to the same challenge. I’ve tried different ways I thought of, most of them similar to Justins - by relying on Core Data and controller layer. This allows me to solve problems with minimum amount of code. Reading Martins post made me think about my approach to model layer - partially I’ve adopted similar approach, although I still prefer to rely on automatic KVO for binding. However I’ve found some very nice ideas which I incorporated into my workflow’s bag of tricks. In this post I’ll describe some of these. As I tend to use unit testing for (at least) model layer, I’ll also explain the mechanisms I use for swapping different model objects with mockups.
To get unified access to main application’s objects, I used to have a service provider class. However after reading Martins post, I split it to several helper classes:
This is the lowest level class. It provides common services such as the name of the application and support folder handling to the whole application.
It also provides simple access to Foundation and AppKit singletons such as NSNotificationCenter, NSFileManaged and NSWorkspace. In the rest of the application, I always use the objects returned from service provider! Although this might seem like an overkill as these objects are easily accessible through their singleton instances, this comes very convenient when you start doing unit testing.
One commonly used stuff I’ve come across implementing in almost every project of mine is a way to change behavior of the application without the need to recompile it. For example, I might want to prevent saving data when working on import functionality, or simply skip steps which take time and are executed on every startup, especially steps I don’t need for testing I’m working on at the moment. I’ve also used this for simpler functionality testing by changing modes or forcing reporting of errors etc. I do this as a plist saved in application’s support folder and loaded in NSDictionary which is accessible through service provider property.
Low-level Core Data handling, such as stack handling, creating, fetching and deleting objects, error handling etc. I use this class as an entry point for handling managed objects and the rest of Core Data stack. Database provider can have a delegate assigned to it. The delegate is informed about the errors while initializing the stack, fetching etc. I usually make the application’s delegate respond to this and either terminate the application (if Core Data can’t be initialized) or display the error otherwise. This makes the code in the rest of the application less cluttered.
The entry point for the application’s model. It’s implemented as singleton to make it easily accessible for the whole application. It provides access to service and database provider. This is the class that is either overridden or updated in actual projects to include project specific stuff. This is where initial database is created among other things.
Various smaller bits and pieces
In addition with above classes, I also use a category on NSObject which
provides simple access to model layer, simplifies NSError creation and
adds some common methods. The interesting part are the accessors for
GBModelController, GBServiceProvider, GBDatabaseProvider and foundation
and AppKit objects from service provider. I find myself constantly
creating ivars for these in many places, so the accessors allow simple
access to these objects. As I use garbage collection for my projects, I
invalidate method that is used for cleaning up manually
I also use NSDate category that unifies date and time handling for the application (inspired by Matt Gallagher’s post).
Core Data custom entities
I use Jonathan Rentzsch mogenerator for creating custom entities. Not only it solves me the pain of manually updating accessors and mutators on changes, it also adds value accessors and much more. If you’re not using it yet, you owe it to yourself to go check it out! You can even install Xcode plugin called Xmo’d (included in mogenerator download) that automatically updates model files when .xcdatamodel is saved. However I opt to run mogenerator manually to get full control over the files it creates.
Although mogenerator creates completely useful classes, sometimes handling managed objects still requires a higher level code (sort indexes, fetching and unified error reporting are the most frequent reasons I can think of at this moment). Although this code may be implemented in managed object subclasses, many times it makes sense to do it in a separate class. I use either model controller itself or splitting the functionality in various helper classes, one per entity or task. These are all accessible from model controller.
I keep as much of my model layer objects as possible under unit tests. I rarely do unit testing of controller or view layer objects (although I can see plenty of benefits, I test these manually by running the app through well documented use cases). After some late night episodes with SetTestKit, I finally decided to check out alternatives and ended up using Gabriel Handfords GHUnit. I also use OCMock and OCHamcrest, so switching from OCUnit wasn’t that painful at all.
Working with any kind of database presents a bit of a challenge while unit testing to avoid coupling unit tests to results of previous ones. This is also true for using Core Data. However there’s one feature built right in which makes independent and reliable unit testing a breeze - in-memory data store.
All of my model layer objects are fully prepared for this -
GBModelController has accessor for GBDatabaseProvider for example. The
accessor simply lazily creates default instance on first usage, unless
an object was already assigned. So unit tests simply need to assign
in-memory database provider before sending any other message and we’re
fine! And while at it - GBDatabaseProvider has a convenience initializer
initWithInMemoryStoreType that creates an instance for such
As most of the unit tested objects require access to database, I find
myself overriding setup and teardown methods to handle database
properly. To unify that, I use a common base class
GBDatabaseTestingBase. It simply sets GBModelController’s database
provider to in-memory version in setup and cleans it back to
teardown. Then each test can be executed predictively in isolation. I
also use this class to add all non-trivial managed objects creation
methods, such as creation of complex object graphs with relationships
Some of the objects required for unit testing, need to be cleaned up
regardless of whether the test succeeds or fails. Usually these objects
need to be sent
invalidate to properly cleanup. For this purpose, I
use GBTestObjectsRegistry class. It’s a simple wrapper over an
NSMutableArray and makes sure all registered objects are properly
disposed when test finishes. GBDatabaseTestingBase already includes this
object and sends it
invalidate in teardown, but test cases that don’t
derive from it, can still use it in the same manner.
Futher more, the class also automates notification and KVO observers creation and cleanup, injecting of various debugging options and cleanup. This also serves as an object factory for creating commonly used application objects used by various unit tests - if necessary all created objects are properly registered so that they are invalidated at the end of test.
The rest of the stuff
I use BHLogger with some of my custom extensions for logging purposes. It’s a simple library that allows me to keep my logging code in deployed application and optionally enable or disable various levels. Further more, coupled with BHLog Viewer, it can send log info to the viewer which then allows me to filter it to my needs. The viewer is also usable to be sent to customers which have problems we can’t solve remotely - they would just open the viewer, run the application and replicate the problem, then send generated database to me for viewing. I’ve updated the logger library to automatically log everything if the viewer is detected. I also plan to update the viewer to make it more suitable for end-user usage.
I also use Jonathan Dann and Cathy Shive XSViewController for handling window and view controllers. In addition to stuff implemented there, I use GBWindowController and GBViewController subclasses that add various application-level functionality such as handling currently active view controller and application specific stuff such as notifications handling (ussually application delegate hooks to various notifications from the model and then sends messages to all window controllers which in turn notify view controllers).
As I often require passing notifications from secondary to main thread, I use parts of Dave Dribin DDFoundation to simplify that. And for singleton implementation, I use Matt Galaghers synthesize singleton.
I tend to use “sandbox” versions of all third-party code to avoid breaking the project after updating the code some time in future. The downloadable project templates contain all above mentioned third-party code, including my extensions as described.
Another thing of notice is folder structure of template projects - I like to keep it tidy for cases where I need to browse the project folder in Finder. I use group’s path setting on group’s info general tab to automate that.
Copy Xcode project and file templates from the link below to
~/Library/Application Support/Developer/Shared/Xcode folder. When you
next create a new project or file, you’ll find the options in user
templates (no need to restart Xcode). The templates are tailored to suit
my specific needs and workflow, feel free to use/modify them to your
Oh, and let’s not to forget - of course I use appledoc :) for code documentation, so you can find the classes in templates fully documented (however as they say the blacksmiths horse is always bare-foot, I was too busy (read lazy) to actually test this thoroughly).
At the end I can only thank to the great Mac developer community without which my life would be much more difficult. Thanks to all of you guys!
That’s it for now. In a later post I might describe the specifics of how I extend the framework presented here in a real-life project. When I get the time…