Bundle的加载
NSBundle类提供了加载Cocoa Bundle的方法。本节描述在Cocoa应用程序中加载bundle的基础知识。还讨论了从Cocoa应用程序加载非Cocoa 的Bundle。本材料适用于任何在其应用程序中使用可加载Bundle的开发人员。
使用NSBundle加载Cocoa bundle
NSBundle类提供了从Cocoa bundle中加载可执行代码和资源的方法。它处理加载的所有细节,包括与Mach-O加载器dyld的交互,以及将Objective-C符号加载到Objective-C运行时。
有关使用NSBundle加载非代码资源的信息,请参阅Resource Programming Guide。
加载Cocoa bundle包含五个基本步骤:
- 找到bundle。
- 创建一个NSBundle对象来表示bundle。
- 加载bundle的可执行代码。
- 查询bundle以获取其主体类。
- 实例化主体类的对象
下面的小节将详细介绍这些步骤。
找到bundle
您的应用程序可以从任何位置加载bundle,但是如果它们存储在标准位置,则可以使用Cocoa提供的函数和方法轻松地找到它们。
与应用程序打包在一起的可加载的bundle通常包含在Contents/PlugIns
中的应用程序bundle内。 要检索主应用程序bundle PlugIns目录,请使用NSBundle的builtInPlugInsPath
方法。
这段代码展示了如何使用NSBundle来检索一个应用程序的PlugIns目录,它可以被命名为PlugIns或Plug-ins(前者取代了后者)
1
2
3
4
5
NSBundle *appBundle;
NSString *plugInsPath;
appBundle = [NSBundle mainBundle];
plugInsPath = [appBundle builtInPlugInsPath];
尽管它不是标准位置,但是您可以通过将可加载的bundle存储在应用程序bundle的Resources目录中来获得一些便利。 然后,您可以使用NSBundle的pathsForResourcesOfType:inDirectory:
方法查找它们。 此代码段在应用程序的Resources/PlugIns
目录中找到所有扩展名为.bundle
的文件和目录:
1
2
3
4
5
NSBundle *appBundle;
NSArray *bundlePaths;
appBundle = [NSBundle mainBundle];
bundlePaths = [appBundle pathsForResourcesOfType:@"bundle" inDirectory:@"PlugIns"];
您的应用程序可能还会在多个domain的“Library”目录内的应用程序支持目录中支持bundle:
- 用户指定(〜/Library)
- 系统范围(/Library)
- 网络(/Network/Library)
要搜索这些目录和其他标准目录,请使用NSSearchPathForDirectoriesInDomains
函数。
这个代码片段为您的应用程序创建了一个搜索路径数组来查找bundle,然后您可以搜索各个插件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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]];
}
创建一个NSBundle对象来表示bundle
要为你想加载的bundle创建一个NSBundle对象,要么分配一个对象并使用initWithPath: initializer
,要么使用便利的创建方法bundleWithPath:
。如果bundle的实例已经存在,那么这两个方法都会返回现有的实例,而不是创建一个新的实例。
此代码片段检索位于fullPath的bundle:
1
2
3
4
5
NSString *fullPath; // Assume this exists.
NSBundle *bundle;
bundle = [NSBundle bundleWithPath:fullPath];
加载bundle的可执行代码
要加载一个bundle的可执行代码,使用NSBundle的load
方法。如果加载成功或代码已经加载,则此方法返回YES
,否则返回NO
。
此代码片段在fullPath加载bundle的代码:
1
2
3
4
5
6
NSString *fullPath; // Assume this exists.
NSBundle *bundle;
bundle = [NSBundle bundleWithPath:fullPath];
[bundle load];
查询bundle以获取其主体类
每个Cocoa bundle都包含一个principal类的代码,它通常充当一个应用程序到一个bundle的入口点。你用NSBundle的principalClass
方法来检索一个bundle的主体类,如果它还没有被加载,这个方法会加载这个bundle。此代码片段检索位于fullPath的bundle的主体类:
1
2
3
4
5
6
NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class principalClass;
bundle = [NSBundle bundleWithPath:fullPath];
principalClass = [bundle principalClass];
您还可以使用classNamed:
方法按名称检索类对象。这段代码从fullPath包中检索KillerAppController类:
1
2
3
4
5
6
7
NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class someClass;
bundle = [NSBundle bundleWithPath:fullPath];
someClass = [bundle classNamed:@"KillerAppController"];
实例化主体类的对象
一旦您从可加载的bundle中检索了主体类,您通常会创建类的一个实例来在您的应用程序中使用。(如果类通过类方法提供其所有功能,则不需要此步骤。)为此,您可以像使用任何类名一样使用类变量。
1
2
3
4
5
6
7
8
9
NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class principalClass;
id instance;
bundle = [NSBundle bundleWithPath:fullPath];
principalClass = [bundle principalClass];
instance = [[principalClass alloc] init];
加载Cocoa bundle:示例代码
在大多数应用程序中,包加载的五个步骤发生在启动过程中,在启动过程中搜索并加载插件。清单1显示了一对方法的实现,这些方法定位bundle、创建NSBundle对象、加载它们的代码,并查找和实例化每个发现的bundle的主体类。清单后面有一个解释。
Listing 1 Method implementations for loading bundles from various locations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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;
}
代码的工作方式如下:
instances
数组包含从发现的包的主体类中实例化的所有对象。为了清晰起见,这个对象显示在方法中,但通常是控制器类的一个实例变量loadAllBundles
方法调用allBundles
方法来检索以扩展名.bundle
结尾的所有文件。allBundles方法只是枚举可加载bundle的所有标准路径(在应用程序包以及用户、本地和网络库目录中)。- 对于每个返回的路径,都会创建一个NSBundle对象。如果扩展名为
.bundle
的文件实际上不是一个有效的bundle, NSBundle返回nil,剩下的迭代被跳过。 - 这一行检索当前包的主体类。调用
principalClass
隐式地先加载代码。 - 最后,该方法实例化主体类。只要
init
不返回nil,就会将新实例添加到实例数组中。如果您正在编写具有插件体系结构的应用程序(而不是具有几个已知可加载包的应用程序),那么您应该在创建principal类的实例之前对插件执行某种验证。
使用CFBundle加载非cocoa包
在某些情况下,您可能需要从Cocoa应用程序中加载非Cocoa bundle。您可以使用Core Foundation中的CFBundle
例程来加载非cocoa bundle:
CFBundleCreate
来创建CFBundle对象;CFBundleLoadExecutable
加载包的可执行代码;CFBundleGetFunctionPointerForName
来查找加载的例程的地址。
有关这些方法和CFBundle提供的其他方法的更多信息,请参阅Core Foundation编程主题Bundle Programming Guide。
要更干净地将代码与Cocoa应用程序集成,您可以编写一个包装类来封装通过CFBundle查找的数据和函数指针。
清单2显示了CFBundle的Cocoa包装类的接口,清单3显示了它的实现。每个列表后面都有一个解释。
Listing 2 Loading and using code from a non-Cocoa bundle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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
该接口包含四个元素:
- 1 函数指针的类型定义,Bundle中每个函数的类型定义
- 2 函数指针实例变量
- 3 CFBundleRef实例变量
- 4 包装C函数的Objective-C方法
Listing 3 Loading and using code from a non-Cocoa bundle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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
下面是实现中所做的事:
- 1 使用指向应用程序插件目录中的bundle的URL初始化
cfBundle
实例变量。bundle可以驻留在磁盘上的任何位置;插件目录只是内置可加载的 bundle的典型位置。 - 2 当调用该方法时,惰性地初始化与该方法关联的函数指针。在查找函数指针之前,对
CFBundleGetFunctionPointerForName
的调用将隐式加载包的可执行代码。 - 3 返回已加载函数返回的值。