82: Cocoa PlugIns Architecture (Loadable Bundles) Example

Problem: to load plugins into the application bundle so that changes in plugin code are automatically propagated each time the application is built.

The example concentrates on how to get XCode to load the plugin file automatically into the application bundle. The plugin implements a single method:

- (NSInteger)addOneInteger:(NSInteger)pInt1 toAnother:(NSInteger)pInt2;

The example assumes automatic Garbage Collection.

Plugin developers note:

The host application and embedded plugins constitute a single name space. In order to use the same class in different plugins you need either
i. to give it a different name in each one or
ii. to place it in a framework and add the framework to the plugins and host application as described in Embedded Frameworks Examples 80 or 81.
iii. Do not include a Copy Files Phase for an Embedded Framework added to the plugin.
iv. After creating a new class in the Framework check that you have set its header's role to public (see my examples). That can save you a lot of grief.
v. After updating the Framework do a Clean before you Build. This ensures that your plugins have access to the updated version. To check that a plugin can access the changes made to a header look inside the plugin's Framework's Headers folder in the Linked Framework part of XCode's Groups And Files.


Overall Process

  • Use XCode to write the application. Call it BaseApp. As part of this write a formal protocol header file: AppPluginProtocol.h.

  • Write the Plugin. Call it PluginPart. As part of this write the principal class: e.g. PluginPrincipalClass.m and .h, which when instantiated constitutes the interface betwen the PluginPart and the BaseApp. It adopts the AppPluginProtocol.h.

  • Bring up the Info panel for the PluginPart Target and in the Properties pane set the Principal Class and the Plugin Identifier. Switch to the the Build pane and change the Wrapper Extension from bundle to plugin

  • Use Project->Add to Project to add the PluginPart.xcodeproj to the BaseApp project. Add the PluginPart.plugin as a dependency to the BaseApp Target. Add a Copy Files Phase to the Plugins folder of the BaseApp Target. Copy the PluginPart.plugin (located in the added PluginPart.xcodeproj) to the Copy Files folder of the BaseApp Target.

  • Build and run. Any changes you now make to the PluginPart project will also be present in the next build of the BaseApp.


1: Write the Application including the Formal Protocol header file

// AppPluginProtocol.h // #import <Cocoa/Cocoa.h> @protocol AppPluginProtocol - (NSInteger)addOneInteger:(NSInteger)pInt1 toAnother:(NSInteger)pInt2; @end // BaseAppAppDelegate.h // #import <Cocoa/Cocoa.h> @interface BaseAppAppDelegate : NSObject <NSApplicationDelegate> { NSWindow *window; } @property (assign) IBOutlet NSWindow *window; @end // BaseAppAppDelegate.m // #import "BaseAppAppDelegate.h" #import "AppPluginProtocol.h" @implementation BaseAppAppDelegate @synthesize window; - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { // Get the plugin NSString *zStrPlugInsPath = [[NSBundle mainBundle] builtInPlugInsPath]; NSArray *zAryBundlePaths = [NSBundle pathsForResourcesOfType:@"plugin" inDirectory:zStrPlugInsPath]; NSString * zStrPathToPlugin = [zAryBundlePaths lastObject]; NSBundle *znsBundlePlugin = [NSBundle bundleWithPath:zStrPathToPlugin]; // instantiate the principal class and call the method Class zPrincipalClass = [znsBundlePlugin principalClass]; id zPrincipalClassObj = [[zPrincipalClass alloc]init]; NSInteger zInt = [zPrincipalClassObj addOneInteger:100 toAnother:23]; NSLog(@"BaseAppAppDelegate zInt = %d",zInt); } // end applicationWillFinishLaunching @end


2: Write the PluginPart.

In XCode do File->New Project. When the template panel appears select Framework & Library and choose the Bundle and name the project PluginPart. Code the project as follows.

// AppPluginProtocol.h // #import <Cocoa/Cocoa.h> @protocol AppPluginProtocol - (NSInteger)addOneInteger:(NSInteger)pInt1 toAnother:(NSInteger)pInt2; @end // PluginPrincipalClass.h // #import <Cocoa/Cocoa.h> #import "AppPluginProtocol.h" @interface PluginPrincipalClass : NSObject <AppPluginProtocol> { } @end // PluginPrincipalClass.m // #import "PluginPrincipalClass.h" @implementation PluginPrincipalClass - (NSInteger)addOneInteger:(NSInteger)pInt1 toAnother:(NSInteger)pInt2 { NSInteger zIntResult = pInt1 + pInt2 + pInt2; return zIntResult; } // end addOneInteger @end


3: Set the Principal Class and the Plugin Identifier in the Properties pane of the PluginPart Target

In XCode Groups & Files select the PluginPart Target and click the info button to obtain the Info panel. Under the Properties tab set the Principal Class to PluginPrincipalClass, the Identifier to myPluginIdentifier (in practice use the recommended format e.g. uk.co.juliuspaintings.myPluginIdentifier).

In XCode set the Principal Class and Identifier

In XCode set the Principal Class and Identifier


4: Change the PluginPart Wrapper Extension from bundle to plugin

Click on the Build tab and scroll down to the Wrapper Extension entry which will currently say bundle. Double click on bundle and change it to plugin. Your PluginPart will now have the extension .plugin.

Change the wrapper extension from bundle to plugin

Change the Wrapper Extension from bundle to plugin


5: Add the PluginPart.xcodeproj to the BaseApp project.

Open the BaseApp XCode project. Select the baseApp folder in the Groups & Files collumn.

Do Project->Add to Project. Navigate the Finder to the PluginPart project and select PluginPart.xcodeproj. Uncheck the Copy items into destination group's folder check box. Select the Recursively create groups for any added folders radio button. Click on the Add button.

In XCode Project->Add Files, Navigate the Finder to the PluginPart project and select 
		PluginPart.xcodeproj

Navigate the Finder to the PluginPart project and select PluginPart.xcodeproj

In XCode  Project->Add Files, Uncheck the Copy items into destination group's folder check box. 
		Select the Recursively create groups for any added folders radio button.

Uncheck the Copy items into destination group's folder check box. Select the Recursively create groups for any added folders radio button.

In XCode after adding the PluginPart.xcodeproj

The XCode window after adding the PluginPart.xcodeproj


6: Add the PluginPart.plugin as a dependency to the BaseApp Target.

Select the BaseApp Target in the Groups & Files collumn of the XCode window. Click on the Info button and select the General tab. Click on the Plus (+) button under the Direct Dependencies field. When the panel appears select the PluginPart and click the Add to Target button. This causes the PluginPart to be built before the BaseApp.

Make the application dependent on the Plugin

Make the application dependent on the Plugin

After making the application dependent on the Plugin

After making the application dependent on the Plugin


7: Add a Copy Files Phase to the BaseApp Target.

Select the BaseApp Target in the Groups & Files collumn of the XCode window. Do Project->New Build Phase->New Copy Files Build Phase which brings up the Copy Files Phase Info panel. Set the Destination pop-up menu to Plugins. This will create the Plugins folder in the Contents folder of the application bundle into which the plugin bundle will be copied. Note that the Path text field is used for specifying a folder relative to the Plugins folder. Leave the Copy only when installing check box unchecked.

Project->Add to Project->Add Copy Files Build Phase

Project->Add to Project->Add Copy Files Build Phase

Copy Files Build Phase Destination Plugins

Set the Copy Files Destination to Plugins. Leave the Path text field empty.


8: Copy the PluginPart.plugin into the Copy Files folder.

The PluginPart.plugin inside the PluginPart.xcodeproj folder under Groups & Files represents the build product of the PluginPart project.

Drag the PluginPart.plugin from the PluginPart.xcodeproj folder in Groups & Files to the BaseApp Copy Files folder.

Drag the PluginPart.plugin from the PluginPart.xcodeproj folder in Groups & Files to the BaseApp Copy Files folder

Drag the PluginPart.plugin from the PluginPart.xcodeproj folder to the BaseApp Copy Files folder

The plugin represented inside the Copy Files folder

The PluginPart.plugin represented inside the Copy Files folder


9: Build the application and examine the application bundle.

Build the BaseApp project. In the Finder select the BaseApp.app file in the BaseApp/build/Debug folder and show its contents. You should see the PluginPart.plugin inside the Contents/Plugins folder.

Contents of the BaseApp's application bundle Contents/Plugins folder

Contents of the BaseApp's application bundle Contents/Plugins folder

Run the BaseApp and note the output in the console. Go into the PluginPart project and make a small modification to the addOneInteger:toAnother: method so it makes a different computation. Save but don't build it. Go back to the BaseApp project and Build and Run. You will see a different result in the console. This shows that the PluginPart project was built as a result of building the BaseApp project.


10: Example code.

The preceding code is available as a zip file 082-Plugin-Code.zip (4.6MB).



Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 11:57 Monday 7th. November 2011