My application Property Manager uses CoreData for its document model and storage. Because the Mac App Store is now requesting that applications are sandboxed, I recently worked on updating Property Manager to work correctly in the sandbox.
Problem Identification
As Property Manager acquires new features (in this case, additional tagging and notation facilities), the data model needs to be updated. This in turn depends on CoreData to migrate the user's previous documents to the latest data model. Traditionally, CoreData migrates the data transparently when the user opens the file, however here lies the problem with sandboxing: the updated file cannot be saved to disk unless the user is presented a NSSavePanel. Because of this, automatic migrations will fail.
As a secondary problem, CoreData only migrates from one version to the next, either by inferring the mapping model or by selecting a single mapping model. If a user migrates from v2 file format to v3, CoreData will handle this automatically. However, CoreData will not handle the case where the user has v1 data which needs to be converted to v2 and then finally v3. In general this seems like very fragile behavior for an application that is managing important user data.
Solution
It turns out that the solution to the above problem is fairly involved, and I ended up rewriting some code (originally written by Marcus S. Zarra in his book Core Data).
In order to kick off the new migration process, we need to hijack the document creation process and check if migration is required:
Finally, the migration controller code incrementally migrates from one model to the next until it gets to the targetModel.
Sandboxing Problems
I would like to say that the above code works flawlessly. In fact it does, under a non-sandbox environment. At this point in time, I've found various bugs and problems with NSSavePanel when used in the sandbox, although these problems don't seem to be officially acknowledged by Apple. The biggest road block I ran into is that essentially savePanel.URL is always nil in the sandbox environment... Hopefully this will be fixed in a future release.
However, at the very least, migrating more than one model works perfectly, and the user is given the opportunity to save their datafile in a new location which at least means that the data won't be damaged by a failed migration.
You can debug the migration process by adding the launch argument -com.apple.CoreData.MigrationDebug 1 to your application. It should print out quite a bit of information.
In my case, my PropertyManager*-*.xcmappingmodel files are listed in the “Compile Sources” build phase for my application, so I can only assume they end up in the main application bundle.
The error message you are receiving is given when there is no migration found from the current Core Data model to some other Core Data model. This may happen if you accidentally modified one of the intermediate managed object models. I suggest you start with the above debug information and try to find out how far it is getting through the migration process.
Feel free to provide more details and I’ll see what I can do to help.
Thanks for helping. In the app I have now, I am running two mapping models to go from version 1→2→3. Using your debugging suggestion I found half of my hash tags in both the source and destination do not match.
ie: Version 1 test File Source does not match 1st Mapper Source (1→2)
Version 2 test File Source does not match 2nd Mapper Source (2→3)
I’m guessing this is why I get “No Mapping Model found”. Is there a way to fix the hash tags?
Thanks for the sample code. In my sandboxed app I too had the problem of savepanel.url returning nil. After some experimenting, I thought I’d try passing the same URL to both arguments of the migrationController in makeDocumentWithContentsOfURL like this:
if ([migrationController migrateURL:url toURL:url error:outError])
And it worked! No savepanel needed!
I should also mention that is being discussed on the Apple dev forums.
Thanks for the good work. It helped me a lot. Two things I find out:
First, the save panel delivers url as nil in a sandbox. It looks like, that the sandbox does not like to initialize the directory url with a file path. You have to set the following:
Comments
Hi Samuel,
Is there a trick to where the mapping model should be? or a naming convention that should be used for the mapping models?
I keep triggering the “No mapping model found!” NSLog.
I know the models work since I can migrate when using temporary entitlements. I just can’t figure out how to make the program manually find them.
Hi Jo3rw,
You can debug the migration process by adding the launch argument
-com.apple.CoreData.MigrationDebug 1
to your application. It should print out quite a bit of information.In my case, my
PropertyManager*-*.xcmappingmodel
files are listed in the “Compile Sources” build phase for my application, so I can only assume they end up in the main application bundle.The error message you are receiving is given when there is no migration found from the current Core Data model to some other Core Data model. This may happen if you accidentally modified one of the intermediate managed object models. I suggest you start with the above debug information and try to find out how far it is getting through the migration process.
Feel free to provide more details and I’ll see what I can do to help.
Kind regards,
Samuel
Hi Samuel,
Thanks for helping. In the app I have now, I am running two mapping models to go from version 1→2→3. Using your debugging suggestion I found half of my hash tags in both the source and destination do not match.
ie: Version 1 test File Source does not match 1st Mapper Source (1→2)
Version 2 test File Source does not match 2nd Mapper Source (2→3)
I’m guessing this is why I get “No Mapping Model found”. Is there a way to fix the hash tags?
@Jo3rw The hash tags come from the models themselves. You might want to try recreating any mapping models with invalid hash tags.
Thanks for the sample code. In my sandboxed app I too had the problem of savepanel.url returning nil. After some experimenting, I thought I’d try passing the same URL to both arguments of the
migrationController
inmakeDocumentWithContentsOfURL
like this:And it worked! No savepanel needed!
I should also mention that is being discussed on the Apple dev forums.
Thanks for the good work. It helped me a lot. Two things I find out:
First, the save panel delivers url as
nil
in a sandbox. It looks like, that the sandbox does not like to initialize the directory url with a file path. You have to set the following:and it will work now.
At at second, you have to set the storeType explicit to
NSSQLiteStoreType
, if you use SQLlite storage. This worked fine for me.Leave a comment
Please note, comments must be formatted using Markdown. Links can be enclosed in angle brackets, e.g.
<www.codeotaku.com>
.