SHORT VERSION:
How can I load classes during runtime from a bundle, when those classes inherit from superclasses not defined in the bundle, but from another framework which was loaded prior? When loading my framework, classes contained therein work fine. When loading the plugin afterwards, any classes which inherit from NSObject work fine but classes inheriting from the framework are not being loaded.
LONG VERSION:
Cocoa allows you to control the Dock icon explicitly via the "Dock Tile Plugin" mechanism. In a nutshell, when your app is added to the Dock, the OS looks inside the app's bundle plugins directory for a bundle called "MyDockTilePlugin.docktileplugin". It then loads this plugin's principal class which must conform to the NSDockTilePlugIn protocol.
I have built such a bundle according to Apple's guide. Now I want to reuse this code by making the component classes superclasses in my custom framework. Subclasses specific to a given project can load the framework.
Surprisingly, this is not very straightforward.
Here is the process in psuedocode:
- The docktileplugin is loaded by the Dock.
- In its +initialize class method, the class loads the custom framework, which is set to "optional" in build settings.
- The class then loads its own plugin which contains classes inheriting from framework classes.
- Something like NSLog(@"%@", NSStringFromClass(NSClassFromString(classInheritingFromFramework))); will always display (null)
How does inheritance work across plugin bundles? THANKS...
If this isn't enough information, please ask for more. I'm trying to be concise and thorough.
MORE CODE:
+ (void)initialize {
[[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
NSLog(@"bundle loaded %@", note);
}];
//Plugin bundle
NSBundle *selfBundle = [NSBundle bundleForClass:[self class]];
//Load Custom Framework
NSString *frameworkName = @"Custom.framwork";
NSString *frameworkPath = [[selfBundle bundlePath] stringByAppendingPathComponent:[@"Contents/Frameworks" stringByAppendingPathComponent:frameworkName]];
NSLog(@"Framework %@ at %@", ([[NSFileManager defaultManager] fileExistsAtPath:frameworkPath]? @"EXISTS" : @"DOES NOT EXISTS"), frameworkPath);
if( [[NSBundle bundleWithPath:frameworkPath] load] ) {
NSLog(@"Framework loaded...");
NSLog(@"Framework provided classes such as DockTilePlugin (%@) and UndoManager (%@)", NSStringFromClass(NSClassFromString(@"DockTilePlugin")), NSStringFromClass(NSClassFromString(@"UndoManager")));
} else {
NSLog(@"Error, framework failed to load.");
exit(1);
}
//Load Dock Tile Plugin
NSString *pluginName = [[selfBundle infoDictionary] objectForKey:@"DockTilePluginCoreBundle"];
NSString *pluginPath = [[selfBundle builtInPlugInsPath] stringByAppendingPathComponent:[pluginName stringByAppendingPathExtension:@"bundle"]];
NSLog(@"Plugin %@ at %@", ([[NSFileMan开发者_运维百科ager defaultManager] fileExistsAtPath:pluginPath]? @"EXISTS" : @"DOES NOT EXISTS"), pluginPath);
if( [[NSBundle bundleWithPath:pluginPath] load] ) {
NSLog(@"Plugin loaded...");
NSLog(@"Plugin provided classes such as DockTilePlugin (%@) and DockTileView (%@)", NSStringFromClass(NSClassFromString(@"DockTilePlugin")), NSStringFromClass(NSClassFromString(@"DockTileView")));
} else {
NSLog(@"Error, Plugin failed to load.");
exit(1);
}
}
What output I see in the Console.app is that the first two "classes such as" show up (class names appear), but in the second two I get "Plugin Provided classes such as DockTilePlugin (null) and DockTileView (null)".
Also, the notifications are received for both bundles. In the framework, I see in the "NSLoadedClasses" key that all framework classes are loaded and available. In the Plugin bundle, this list is empty (although there should be several classes from the bundle, but they all inherit from the framework).
If I add a third class to the plugin, which inherits from Cocoa, that class appears fine. Something about bundle-loading a class which inherits from another bundle-loaded class is tricky. That is the heart of it I think.
Mike from http://mikeash.com - an amazing resource - provided me with the tip that solved this problem. The cause of the problem eludes me, but it was fixed by doing two things:
- In the Framework's project, setting its build location to @rpath as described in this article: http://mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html
- In the plugins loading the framework, weak reference during build, don't copy the framework during build, and then set the -rpath build setting to the location of the framework in the containing app. This ensures all items are using the same copy of the framework.
精彩评论