If you're writing an iOS app that uses Core Data then you may well want to ship it with an initial database (which potentially gets over the air updates later on).

On iOS, CoreData stores always use sqlite3 as their backend. You could create a sqlite database directly, but you'd have to reverse engineer the way apple uses sqlite, ensure that you use the same name manging for table and column names, generate the same meta data used for persistent store migration etc. Too brittle for my liking.

Luckily both RubyCocoa and MacRuby allow you to access the Core Data framework from a ruby script. The former is bundled with Mac OS X since 10.5 (you will need to use the system ruby). RubyCocoa has some clunkier syntax because it doesn't have the benefit of the extensions to ruby, however at the moment MacRuby doesn't quite work with Active Record, which is where I was getting my seed data from. If your data is coming from else where, this may not be a problem. Other than the slight syntax differences around the handling of Objective-C's sort-of-named-arguments the code is the same

The RubyCocoa version looks like this

require 'rubygems'
require 'osx/cocoa'

OSX.require_framework 'CoreData'

class CoreDataStore
  def create_entity name, props={}, relationships={}
    entity = OSX::NSEntityDescription.insertNewObjectForEntityForName_inManagedObjectContext(name, context)
    props.each do |k,v|
      entity.setValue_forKey v, k
    end
    relationships.each do |k, objects|
      collection = entity.mutableSetValueForKey(k)
      objects.each {|o| collection.addObject o}
    end
    entity
  end

  def initialize(data_store_path, mom_path)
    @data_store_path = data_store_path
    @mom_path = mom_path
  end

  def context
    @context ||= OSX::NSManagedObjectContext.alloc.init.tap do |context|
      model = OSX::NSManagedObjectModel.alloc.initWithContentsOfURL(
          OSX::NSURL.fileURLWithPath(@mom_path))
      coordinator = OSX::NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)

      result, error = coordinator.addPersistentStoreWithType_configuration_URL_options_error(
         OSX::NSSQLiteStoreType, nil, OSX::NSURL.fileURLWithPath(@data_store_path), nil)
      if !result
        raise "Add persistent store failed: #{error.description}"
      end
      context.setPersistentStoreCoordinator coordinator
    end
  end

  def save
    res, error = context.save_
    if !res
      raise "Save failed: #{error.description}"
    end
    res
  end
end

Having done this, you use it like this

store = CoreDataStore.new('seedData.sqlite', 'yourmodel.mom')
blog = store.create_entity 'Blog', 'title' => 'Hello world', 'body' => "it's a fine day"
store.create_entity 'Comment', 'body' => 'I Agree', 'blog' => blog
store.save

The too arguments are the path to the file you want to store the data in, and the path to your model file.

Your Mom's a data model

You may be wondering what a .mom file is. In XCode you work with a .xcdatamodel file. When you build your app, this is compiled down into a .mom file. You can also do it your self by running /Developer/usr/bin/momc mymodel.xcdatamodel mymodel.mom

I created a new iPhone project today and got a rather vague error message from Xcode when I tried to run it on a device:

Error launching remote program: security policy error

After some digging it turns out that the problem was that I had two provisioning profiles on the device:

  • a wildcarded one (which had expired) that I use for quick hacks that don't warrant going through the hassle of setting up new provision profiles
  • one created specifically for this application

The wildcarded profile had expired, and for some reason the iPhone was trying to launch the app with the expired profile even though it had a valid profile that it could have used. Deleting the expired profile from the device did the trick. If only that error message had been a bit more explicit ...

My first Iphone app

October 12th, 2009

After much toiling kgb's first iPhone app is on sale! Get it now. Only in the US unfortunately.

For the record, we submitted two versions of the app: an earlier version that we didn't expect to release (we wanted to see what the approval process was like and if apple was OK with the basic idea of our app) and the one that is on sale today. Both were approved in about 10 days - no approval horror stories here!

Unit testing Core Data iphone apps

September 10th, 2009

This is probably obvious, but in the interest of saving someone else the few minutes I spent scratching my head on this one...

Your app probably has some code that looks like

managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]

If you pass nil then the main bundle is searched. When your an iphone app, that is the app itself - no surprises there. But when you are a unit test bundle, you are no longer the main bundle (the test rig application is). You need to tell CoreData to look inside your unit test bundle, with something along the lines of

managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:
[NSArray arrayWithObject:[NSBundle bundleWithIdentifier:@"com.yourcompany.unittests"]]] retain];    

For this to work you also need your data model to be present in your unit test bundle. For some reason, unlike your .m files the inspector doesn't show you a list of targets the model should be included in - you need to drag the data model file into the compile source build phase of your unit test target.