“
Thanks to the ability to have configurations in a Core Data Managed Object Model and being able to save data to multiple Persistent Stores, it is possible to have a Core Data Model that is constructed from not only an internal model, but from the models of all the plug-ins that are loaded into the application.
In this example we are going to build a basic application with the following requirements:
Core Data allows a NSManagedObjectModel to be constructed from multiple ‘sub’ models. Therefore we can load up all of our plug-ins and ask them for their NSManagedObjectModel references. Using those plus the models included with the application we can build a composite model.
However this would still save into a single file which would then become fragile if a plug-in disappeared. Instead we will use Core Data Configurations. By declaring a configuration for each model, we can specify a different file on disk for each configuration and thereby be able to split the persistent store by model on disk. If a plug-in disappears, the configuration is not loaded and its corresponding persistent store is not loaded and therefore the integrity of the persistent store stays intact.
In this example we are not going to explore Plug-in design in too much depth. That subject has been covered elsewhere. Our plug-in for this design is going to use a framework to host the shared code and both the application and the bundles will link to it and import from it.
Our framework is going to consist of a header file that defines the protocol that the plug-ins must implement to be loaded. The header is as follows:
@protocol ZSPlugin <NSObject> ' - (NSString*)name; - (NSString*)modelConfigurationName; - (NSManagedObjectModel*)managedObjectModel; ' @end
To test this we need to include at least one plug-in. That plug-in will consist of a principal class along with a data model. The plug-in does not need to stand up a Core Data stack because its model will be included in the primary application’s Core Data stack. Therefore we just need to implement the methods in the protocol.
The name method in this example just returns a string.
- (NSString*)name; { return @'Example Plugin v1.0'; }
Like the name method above, the -modelConfigurationName method only returns a string. In a more robust solution we would check in the plug-in manager to confirm that this name is unique and does not conflict with the configuration of the base application.
- (NSString*)modelConfigurationName; { return @'ExamplePlugin'; }
The final method that we declare in the protocol returns the NSManagedObjectModel for the plug-in. This method does a simple load from the plug-ins bundle.
- (NSManagedObjectModel*)managedObjectModel; { if (managedObjectModel) return managedObjectModel; ' NSBundle *myBundle = [NSBundle bundleForClass:[self class]]; NSArray *bundles = [NSArray arrayWithObject:myBundle]; managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:bundles] retain]; return managedObjectModel; }
The one interesting part of this code is that we need to get a reference to the NSBundle for the plug-in from its principal class. This is because the call [NSBundle mainBundle] will return the application’s bundle instead.
The plug-in manager loads all of the available plug-ins on launch. In this example we do not allow dynamic plug-in loading.
#import <ZSPlugin/ZSPlugin.h> ' @interface ZSPluginManager : NSObject { NSArray *loadedPlugins; } ' @property (retain) NSArray *loadedPlugins; ' + (id)shared; ' - (NSArray*)pluginModels; - (NSArray*)modelConfigurations; - (NSString*)applicationSupportFolder; ' @end
-initThe plug-in manager is a singleton that initializes itself and loads all of the existing plug-ins upon first request.
- (id)init { if (!(self = [super init])) return nil; ' //Find the plugins NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *plugins = [fileManager directoryContentsAtPath:[self applicationSupportFolder]]; ' if (![plugins count]) { NSLog(@'%@:%s No plugins found', [self class], _cmd); return self; } ' //Load all of the plugins NSMutableArray *loadArray = [NSMutableArray array]; for (NSString *pluginPath in plugins) { if (![pluginPath hasSuffix:@'.bundle']) continue; NSBundle *pluginBundle = [NSBundle bundleWithPath:pluginPath]; Class principalClass = [pluginBundle principalClass]; if (![principalClass conformsToProtocol:@protocol(ZSPlugin)]) { NSLog(@'Invalid plug-in, does not conform to the ZSPlugin Protocol: %@', pluginPath); continue; } id<ZSPlugin> plugin = [[principalClass alloc] init]; [loadArray addObject:plugin]; NSLog(@'Plug-in Loaded: %@', [plugin name]); [plugin release], plugin = nil; } [self setLoadedPlugins:loadArray]; ' return self; }
In the -init method we first find all of the files in the Application Support directory. If there are no files then we quickly return self. If there are files then we start looping over them. On each iteration we check to see if the file is a plug-in and if it is not we skip to the next loop. If it is then we look-up its principal class and confirm that it conforms to the ZSPlugin protocol. If it does not we warn the developer and skip to the next loop.
Once we pass all of the integrity checks we then call alloc and init on the plug-in and add it to the array of loaded plug-ins.
-pluginModelsTo help the main application initialize we have a couple of helper methods in the plug-in manager. The first is a method that returns all of the plug-in models.
- (NSArray*)pluginModels; { NSMutableArray *array = [NSMutableArray array]; for (id<ZSPlugin> plugin in [self loadedPlugins]) { [array addObject:[plugin managedObjectModel]]; } return array; }
-modelConfigurationsThe second helper method returns an NSArray of the configuration names used by the plug-ins. These names will be used both to load the configurations and to decide on the persistent store’s file name.
- (NSArray*)modelConfigurations; { NSMutableArray *array = [NSMutableArray array]; for (id<ZSPlugin> plugin in [self loadedPlugins]) { [array addObject:[plugin modelConfigurationName]]; } return array; }
With our quick walkthrough of the plug-in structure complete we need to review the changes to the application itself. All of the changes are limited to the Core Data methods.
-managedObjectModelThe first change we need to make is how we load the NSManagedObjectModel. Normally we would just call [NSManagedObjectModel mergedModelFromBundles:nil] and be done. However we want to load not only the models within the application itself but also merge with all of the models in the plug-ins. Therefore a couple of extra steps are required.
- (NSManagedObjectModel*)managedObjectModel { if (managedObjectModel) return managedObjectModel; ' NSMutableArray *models = [NSMutableArray array]; [models addObject:[NSManagedObjectModel mergedModelFromBundles:nil]]; [models addObjectsFromArray:[[ZSPluginManager shared] pluginModels]]; ' managedObjectModel = [[NSManagedObjectModel modelByMergingModels:models] retain]; return managedObjectModel; }
In this method we start with the NSManagedObjectModel from the Application itself and add it to a NSMutableArray. We then request an array of all the models from the plug-ins via the ZSPluginManager and add those models to the NSMutableArray. Once we have all of the models together we call +modelByMergingModels: and merge all of the models into one super model. We retain that model and return it to the caller.
-persistentStoreCoordinatorThe second and last change we need to make to the Core Data stack is the way that we handle the NSPersistentStoreCoordinator.
- (NSPersistentStoreCoordinator*)persistentStoreCoordinator { if (persistentStoreCoordinator) return persistentStoreCoordinator; ' NSFileManager *fileManager; NSString *applicationSupportFolder = nil; NSURL *url = nil; NSError *error = nil; NSString *filePath = nil; ' fileManager = [NSFileManager defaultManager]; applicationSupportFolder = [[ZSPluginManager shared] applicationSupportFolder]; if (![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) { if (![fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil]) { NSLog(@'%@:%s Failed to create app support directory', [self class], _cmd); return nil; } } ' NSManagedObjectModel *mom = [self managedObjectModel]; if (!mom) return nil; NSPersistentStoreCoordinator *psc = nil; psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; ' NSMutableArray *configArray = [NSMutableArray array]; [configArray addObject:@'Core']; [configArray addObjectsFromArray:[[ZSPluginManager shared] modelConfigurations]]; ' for (NSString*configName in [[ZSPluginManager shared] loadedPlugins]) { filePath = [configName stringByAppendingPathExtension:@'sqlite']; filePath = [applicationSupportFolder stringByAppendingPathComponent:filePath]; url = [NSURL fileURLWithPath:filePath]; if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:configName URL:url options:nil error:&error]) { ' [[NSApplication sharedApplication] presentError:error]; [psc release], psc = nil; return nil; } } ' persistentStoreCoordinator = psc; return persistentStoreCoordinator; }
The first half of the method is almost straight out of the template. However once we have the application support directory created and the raw NSPersistentStoreCoordinator initialized it is time to deviate.
The first then we do is construct a NSMutableArray and add our application model’s configuration name to it. In this example I called it ‘Core’. With the array initialized we then grab all of the configuration names from the plug-ins via the ZSPluginManager and add them to the array.
With the array fully populated it is time to iterate over it. Within each iteration we do the following:
We construct a file path for the configuration using the application support folder and the configuration name. We add a ‘sqlite’ extension to it although in a production system you should use an application specific extension.
We then add the newly constructed file path to the NSPersistentStoreCoordinator re-using the configuration name and checking for failure. If we fail we present an error and abort.
That is all there is to it! Core Data takes over from there and automatically saves the correct objects into the correct store files for us. If a plug-in goes away we don’t load its model and we don’t reference it’s store so integrity remains.
In the included example application I have also built a UI which lists the entities that are available in the model. If you run it straight from the zip file you will see the following UI:

However, if after building the application, you copy the example plugin into your ~/Library/Application Support/CDPlugins directory and run the application again you will see the following.

Which shows that not only is the plug-in loaded but that it’s entity (Widget) has been included in the Core Data stack. This can even be extended to create widgets and then remove the plug-in to confirm that integrity is maintained.
“
(Via Cocoa Is My Girlfriend.) Original Link: Core Data and Plug-ins