Loading Bundles
The NSBundle class provides methods for loading Cocoa bundles. This section describes the basics of bundle loading in a Cocoa application. Also covered are loading non-Cocoa bundles from a Cocoa application. This material is relevant for any developer using loadable bundles in their application.
Loading Cocoa Bundles with NSBundle
The NSBundle class provides methods for loading executable code and resources from Cocoa bundles. It handles all the details of loading, including interacting with the Mach-O loader dyld
and loading Objective-C symbols into the Objective-C runtime.
For information about using NSBundle to load non-code resources, see Resource Programming Guide.
Loading Cocoa bundles consists of five basic steps:
Locate the bundle.
Create an NSBundle object to represent the bundle.
Load the bundle’s executable code.
Query the bundle for its principal class.
Instantiate an object of the principal class.
The following sections cover each of these steps in detail.
Locating Bundles
Your application can load bundles from any location, but if they are stored in standard locations you can use functions and methods provided by Cocoa to find them easily.
Loadable bundles that are packaged with your applications are typically included inside the application bundle in Contents/PlugIns
. To retrieve the plug-in directory for the main application bundle, use NSBundle’s builtInPlugInsPath
method.
This code fragment shows how to use NSBundle to retrieve an application’s plug-in directory, which may be named PlugIns
or Plug-ins
(the former supersedes the latter):
NSBundle *appBundle; |
NSString *plugInsPath; |
appBundle = [NSBundle mainBundle]; |
plugInsPath = [appBundle builtInPlugInsPath]; |
Although it is not the standard location, you can gain some convenience by storing loadable bundles in your application bundle’s Resources
directory. Then you can use NSBundle’s pathsForResourcesOfType:inDirectory:
method to find them. This code fragment finds all files and directories with the extension .bundle
in the application’s Resources/PlugIns
directory:
NSBundle *appBundle; |
NSArray *bundlePaths; |
appBundle = [NSBundle mainBundle]; |
bundlePaths = [appBundle pathsForResourcesOfType:@"bundle" |
inDirectory:@"PlugIns"]; |
Your application may also support bundles in application support directories within the Library
directory in multiple domains: user-specific (~/Library
), system-wide (/Library
), network (/Network/Library
). To search for these and other standard directories, use the NSSearchPathForDirectoriesInDomains
function.
This code fragment creates an array of search paths for your application to find bundles, which you can then search for individual plug-ins:
NSString *appSupportSubpath = @"Application Support/KillerApp/PlugIns"; |
NSArray *librarySearchPaths; |
NSEnumerator *searchPathEnum; |
NSString *currPath; |
NSMutableArray *bundleSearchPaths = [NSMutableArray array]; |
// Find Library directories in all domains except /System |
librarySearchPaths = NSSearchPathForDirectoriesInDomains( |
NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES); |
// Copy each discovered path into an array after adding |
// the Application Support/KillerApp/PlugIns subpath |
searchPathEnum = [librarySearchPaths objectEnumerator]; |
while(currPath = [searchPathEnum nextObject]) |
{ |
[bundleSearchPaths addObject: |
[currPath stringByAppendingPathComponent:appSupportSubpath]]; |
} |
Creating an NSBundle Object
To create an NSBundle object for a bundle you want to load, either allocate an object and use the initWithPath:
initializer or use the convenience creation method bundleWithPath:
. If an instance already exists for the bundle, both of these methods return the existing instance instead of creating a new one.
This code fragment retrieves the bundle located at fullPath
:
NSString *fullPath; // Assume this exists. |
NSBundle *bundle; |
bundle = [NSBundle bundleWithPath:fullPath]; |
Loading Code
To load a bundle’s executable code, use NSBundle’s load
method. This method returns YES
if loading was successful or if the code had already been loaded, and NO
otherwise.
This code fragment loads the code for the bundle at fullPath
:
NSString *fullPath; // Assume this exists. |
NSBundle *bundle; |
bundle = [NSBundle bundleWithPath:fullPath]; |
[bundle load]; |
Retrieving the Principal Class
Every Cocoa bundle contains code for a principal class, which typically serves as an application’s entry point into a bundle. You retrieve a bundle’s principal class with NSBundle’s principalClass
method, which loads the bundle if it is not already loaded. This code fragment retrieves the principal class for the bundle located at fullPath
:
NSString *fullPath; // Assume this exists. |
NSBundle *bundle; |
Class principalClass; |
bundle = [NSBundle bundleWithPath:fullPath]; |
principalClass = [bundle principalClass]; |
You can also retrieve class objects by name with the classNamed:
method. This code fragment retrieves the class KillerAppController from the bundle at fullPath
:
NSString *fullPath; // Assume this exists. |
NSBundle *bundle; |
Class someClass; |
bundle = [NSBundle bundleWithPath:fullPath]; |
someClass = [bundle classNamed:@"KillerAppController"]; |
Instantiating the Principal Class
Once you have retrieved the principal class from a loadable bundle, you typically create an instance of the class to use in your application. (If the class provides all its functionality through class methods, this step is not necessary.) To do this, you use a Class
variable in the same way you would use any class name.
This code fragment retrieves the principal class of the bundle at fullPath
and creates an instance of the principal class:
NSString *fullPath; // Assume this exists. |
NSBundle *bundle; |
Class principalClass; |
id instance; |
bundle = [NSBundle bundleWithPath:fullPath]; |
principalClass = [bundle principalClass]; |
instance = [[principalClass alloc] init]; |
Loading Cocoa Bundles: Example Code
In most applications, the five steps of bundle loading take place during the startup process as it searches for and loads plug-ins. Listing 1 shows the implementation for a pair of methods that locate bundles, create NSBundle objects, load their code, and find and instantiate the principal class of each discovered bundle. An explanation follows the listing.
Listing 1 Method implementations for loading bundles from various locations
NSString *ext = @"bundle"; |
NSString *appSupportSubpath = @"Application Support/KillerApp/PlugIns"; |
// ... |
- (void)loadAllBundles |
{ |
NSMutableArray *instances; // 1 |
NSMutableArray *bundlePaths; |
NSEnumerator *pathEnum; |
NSString *currPath; |
NSBundle *currBundle; |
Class currPrincipalClass; |
id currInstance; |
bundlePaths = [NSMutableArray array]; |
if(!instances) |
{ |
instances = [[NSMutableArray alloc] init]; |
} |
[bundlePaths addObjectsFromArray:[self allBundles]]; // 2 |
pathEnum = [bundlePaths objectEnumerator]; |
while(currPath = [pathEnum nextObject]) |
{ |
currBundle = [NSBundle bundleWithPath:currPath]; // 3 |
if(currBundle) |
{ |
currPrincipalClass = [currBundle principalClass]; // 4 |
if(currPrincipalClass) |
{ |
currInstance = [[currPrincipalClass alloc] init]; // 5 |
if(currInstance) |
{ |
[instances addObject:[currInstance autorelease]]; |
} |
} |
} |
} |
} |
- (NSMutableArray *)allBundles |
{ |
NSArray *librarySearchPaths; |
NSEnumerator *searchPathEnum; |
NSString *currPath; |
NSMutableArray *bundleSearchPaths = [NSMutableArray array]; |
NSMutableArray *allBundles = [NSMutableArray array]; |
librarySearchPaths = NSSearchPathForDirectoriesInDomains( |
NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES); |
searchPathEnum = [librarySearchPaths objectEnumerator]; |
while(currPath = [searchPathEnum nextObject]) |
{ |
[bundleSearchPaths addObject: |
[currPath stringByAppendingPathComponent:appSupportSubpath]]; |
} |
[bundleSearchPaths addObject: |
[[NSBundle mainBundle] builtInPlugInsPath]]; |
searchPathEnum = [bundleSearchPaths objectEnumerator]; |
while(currPath = [searchPathEnum nextObject]) |
{ |
NSDirectoryEnumerator *bundleEnum; |
NSString *currBundlePath; |
bundleEnum = [[NSFileManager defaultManager] |
enumeratorAtPath:currPath]; |
if(bundleEnum) |
{ |
while(currBundlePath = [bundleEnum nextObject]) |
{ |
if([[currBundlePath pathExtension] isEqualToString:ext]) |
{ |
[allBundles addObject:[currPath |
stringByAppendingPathComponent:currBundlePath]]; |
} |
} |
} |
} |
return allBundles; |
} |
Here’s how the code works:
The
instances
array contains all the objects instantiated from the principal classes of the discovered bundles. This object is shown in the method for clarity, but would typically be an instance variable of a controller class.The
loadAllBundles
method calls theallBundles
method to retrieve all files ending with the extension.bundle
. TheallBundles
method just enumerates through all the standard paths for loadable bundles (in the application bundle and in the user, local, and networkLibrary
directories).For each returned path, an NSBundle object is created. If the file with a
.bundle
extension was not in fact a valid bundle, NSBundle returnsnil
and the rest of the iteration is skipped.This line retrieves the principal class of the current bundle. Calling
principalClass
implicitly loads the code first.Finally, the method instantiates the principal class. As long as
init
does not returnnil
, the new instance is added to theinstances
array. If you are writing an application with a plug-in architecture (as opposed to an application with a few known loadable bundles), you should perform some kind of validation on the plug-ins before creating an instance of the principal class.
Loading Non-Cocoa Bundles with CFBundle
In some instances, you may need to load non-Cocoa bundles from within a Cocoa application. You use the CFBundle routines in Core Foundation to load non-Cocoa bundles: CFBundleCreate
to create CFBundle objects; CFBundleLoadExecutable
to load the bundle’s executable code; and CFBundleGetFunctionPointerForName
to look up the address of a loaded routine. See the Core Foundation Programming Topic Bundle Programming Guide for more information about these methods and other methods provided by CFBundle.
To integrate the code more cleanly with your Cocoa application, you can write a wrapper class to encapsulate the data and function pointers looked up through CFBundle.
Listing 2 shows the interface for a Cocoa wrapper class for a CFBundle and Listing 3 shows its implementation. An explanation follows each listing.
Listing 2 Loading and using code from a non-Cocoa bundle
#import <CoreFoundation/CoreFoundation.h> |
typedef long (*DoSomethingPtr)(long); // 1 |
typedef void (*DoSomethingElsePtr)(void); |
@interface MyBundleWrapper : NSObject |
{ |
DoSomethingPtr doSomething; // 2 |
DoSomethingElsePtr doSomethingElse; |
CFBundleRef cfBundle; // 3 |
} |
- (long)doSomething:(long)arg; // 4 |
- (void)doSomethingElse; |
@end |
The interface contains four elements:
Type definitions for function pointers, one for each function in the bundle
Function pointer instance variables
A
CFBundleRef
instance variableObjective-C methods to wrap the C functions
Listing 3 Loading and using code from a non-Cocoa bundle
#import "MyBundleWrapper.h" |
@implementation MyBundleWrapper |
- (id)init |
{ |
NSString *bundlePath; |
NSURL *bundleURL; |
self = [super init]; |
bundlePath = [[[NSBundle mainBundle] builtInPlugInsPath] // 1 |
stringByAppendingPathComponent:@"MyCFBundle.bundle"]; |
bundleURL = [NSURL fileURLWithPath:bundlePath]; |
cfBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)bundleURL); |
return self; |
} |
- (void)dealloc |
{ |
CFRelease(cfBundle); |
} |
- (long)doSomething:(long)arg |
{ |
if(!doSomething) // 2 |
{ |
doSomething = CFBundleGetFunctionPointerForName(cfBundle, |
CFSTR("DoSomething")); |
} |
return doSomething(arg); // 3 |
} |
- (void)doSomethingElse |
{ |
if(!doSomethingElse) // 2 |
{ |
doSomethingElse = CFBundleGetFunctionPointerForName(cfBundle, |
CFSTR("DoSomethingElse")); |
} |
doSomethingElse(); // 3 |
} |
@end |
Here’s what the implementation does:
Initializes the
cfBundle
instance variable with a URL to the bundle in the application’s plug-ins directory. The bundle can reside anywhere on disk; the plug-ins directory is just the typical location for built-in loadable bundles.When the method is called, lazily initializes the function pointer associated with the method. The call to
CFBundleGetFunctionPointerForName
implicitly loads the bundle’s executable code before looking up the function pointer.Returns the value returned by the loaded function.
Copyright © 2003, 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-12-16