Bundle Programming Guide (二)

框架捆绑

框架是封装动态共享库和支持该库所需的资源文件的分层目录。框架比典型的动态共享库提供了一些优势,因为它们为所有框架的相关资源提供了一个单一的位置。例如,大多数框架包括定义框架导出的符号的头文件。使用共享库及其资源对这些文件进行分组,可以更轻松地安装和卸载框架并查找框架的资源。

就像一个动态共享库一样,框架提供了一种将常用代码解析成可以由多个应用程序共享的中央位置的方法。在任何给定的时间,框架的代码和资源只有一个副本驻留在内存中,而不管有多少进程正在使用这些资源。与框架链接的应用程序然后共享包含框架的内存。此行为减少了系统的内存占用,并有助于提高性能。

注意:只有框架的代码和只读资源被共享。如果一个框架定义了可写变量,每个应用程序都将获得自己的这些变量的副本,以防止它们影响其他应用程序。
虽然您可以创建自己的框架,但大多数开发人员的框架体验来自于将其包括在他们的项目中。框架是macOS如何为您的应用程序提供许多关键功能。由macOS提供的公开可用框架位于/ System / Library / Frameworks目录中。在iOS中,公共框架位于相应iOS SDK目录的System / Library / Frameworks目录中。有关向Xcode项目添加框架的信息,请参阅Xcode Build System Guide。

注意:iOS中不支持创建自定义框架。
有关框架和框架包的更多详细信息,请参阅框架编程指南。

框架捆绑解剖

框架束的结构与应用程序和插件使用的结构不同。框架的结构基于一种早于macOS的捆绑格式,并支持在框架包中包含框架的代码和资源的多个版本。这种类型的捆绑包被称为版本捆绑。支持多个版本的框架允许较旧的应用程序继续运行,即使框架共享库继续发展。 bundle的Versions子目录包含单独的框架修订版本,而bundle目录顶部的符号链接指向最新版本。

系统通过其目录名称上的.framework扩展名来标识框架包。系统还使用框架资源目录中的Info.plist文件来收集关于框架配置的信息。清单2-5显示了框架包的基本结构。列表中的箭头( - >)表示指向特定文件和子目录的符号链接。这些符号链接可以方便地访问最新版本的框架。

清单2-5一个简单的框架包

MyFramework.framework/
   MyFramework  -> Versions/Current/MyFramework
   Resources    -> Versions/Current/Resources
   Versions/
      A/
         MyFramework
         Headers/
            MyHeader.h
         Resources/
            English.lproj/
               InfoPlist.strings
            Info.plist
      Current  -> A

框架不需要包含Headers目录,但这样做允许您包括定义框架的导出符号的头文件。框架可以在标准和自定义目录中存储其他资源文件。

创建框架包

如果您正在开发用于macOS的软件,您可以创建自己的自定义框架,并将其私下使用或使其可供其他应用使用。您可以使用单独的Xcode项目或通过向现有项目添加框架目标来创建新框架。

有关如何创建框架的信息,请参阅框架编程指南。

可加载捆绑

插件和其他类型的可加载捆绑包为您提供了一种动态扩展应用程序行为的方法。可装载的包由可执行代码和支持存储在捆绑包目录中的代码所需的任何资源组成。您可以使用可加载的软件包将代码懒惰地加载到应用程序中,或允许其他开发人员扩展应用程序的基本行为。

注意:iOS中不支持创建和使用可加载捆绑包。

可加载捆绑解剖

可加载捆绑基于与应用捆绑相同的结构。在捆绑的顶层是一个单一的目录目录。这个目录中有几个用于存储可执行代码和资源的子目录。 Contents目录还包含捆绑包的Info.plist文件,其中包含有关捆绑包配置的信息。

与应用程序的可执行程序不同,可加载的软件包通常不具有主要功能作为其主要入口点。相反,加载捆绑包的应用程序负责定义预期的入口点。例如,可以期望捆绑包定义具有特定名称的功能,或者可以期望在其Info.plist文件中包含标识要使用的特定功能或类的信息。此选择由用户定义可加载捆绑包的格式。

清单2-6显示了可加载捆绑包的布局。可加载捆绑包的顶级目录可以有任何扩展名,但常见的扩展名包括.bundle和.plugin。 macOS始终将包含这些扩展名的bundle视为包,默认隐藏其内容。

清单2-6一个简单的可加载bundle

MyLoadableBundle.bundle
   Contents/
      Info.plist
      MacOS/
         MyLoadableBundle
      Resources/
         Lizard.jpg
         MyLoadableBundle.icns
         en.lproj/
            MyLoadableBundle.nib
            InfoPlist.strings
         jp.lproj/
            MyLoadableBundle.nib
            InfoPlist.strings

除了MacOS和资源目录之外,可加载捆绑包可能包含其他目录,如框架,插件,SharedFrameworks和SharedSupport - 完整的应用程序包支持的所有功能。

可加载捆绑的基本结构是相同的,无论捆绑在其实现中使用哪种语言。有关可加载捆绑包的结构的更多信息,请参阅代码加载编程主题。

创建一个可装载的软件包

如果您正在开发用于macOS的软件,您可以创建自己的自定义可加载捆绑包,并将它们并入应用程序。如果其他应用程序导出插件API,还可以开发针对这些API的捆绑包。 Xcode包括使用C或Objective-C实现bundle的模板项目,具体取决于预期的目标应用程序。

有关如何使用Objective-C设计可加载软件包的更多信息,请参阅代码加载编程主题。有关如何使用C语言设计可加载软件包的信息,请参阅插件。

捆绑中的本地化资源

在MacOS软件包(或iOS应用程序包的顶层目录)的资源目录中,您可以创建一个或多个特定于语言的项目子目录来存储特定于语言和区域的资源。每个目录的名称基于所需的本地化的语言和区域,后跟.lproj扩展名。要指定语言和区域,请使用以下格式:

language_region.lproj
目录名称的语言部分是符合ISO 639惯例的双字母代码。区域部分也是双字母代码,但它符合用于指定特定区域的ISO 3166约定。虽然目录名称的区域部分是完全可选的,但它可以是调整本地化对于世界特定部分的有用方法。例如,您可以使用一个en.lproj目录来支持所有英语国家。然而,为英国(en_GB.lproj),澳大利亚(en_AU.lproj)和美国(en_US.lproj)提供单独的本地化可让您为每个国家定制您的内容。

注意:为了向后兼容,NSBundle类和CFBundleRef函数还支持几种通用语言的可读目录名称,包括English.lproj,German.lproj,Japanese.lproj等。虽然支持人类可读的名称,但优先使用ISO名称。
如果大多数资源文件对于给定语言的所有区域都相同,则可以将仅语言的资源目录与一个或多个特定于区域的目录相结合。提供两种类型的目录可以减轻对每个支持的区域的每个资源文件的复制。相反,您只能自定义特定区域所需的文件子集。在查找包中的资源时,捆绑包加载代码首先在任何区域特定的目录中,其次是特定于语言的目录。而且,如果本地化目录都不包含资源,则bundle加载代码将查找适当的非本地化资源。

重要提示:不要将代码存储在lproj文件夹中,因为系统没有加载或执行存储在lproj文件夹中的代码。要了解有关应用程序包中应存储代码和其他类型的数据的更多信息,请参阅macOS代码签名深度。

清单2-7显示了包含语言和区域特定资源文件的Mac应用程序的潜在结构。 (在iOS应用程序中,资源目录的内容将位于捆绑包目录的顶层。)请注意,特定于区域的目录仅包含en.lproj目录中的一部分文件。 如果找不到特定于区域的资源版本,则该软件包将查找该资源的特定于语言的目录(在这种情况下为en.lproj)。 特定于语言的目录应始终包含任何特定于语言的资源文件的完整副本。

清单2-7具有本地化资源的软件包

Resources/
   MyApp.icns
   en_GB.lproj/
      MyApp.nib
      bird.tiff
      Localizable.strings
   en_US.lproj/
      MyApp.nib
      Localizable.strings
   en.lproj/
      MyApp.nib
      bird.tiff
      Bye.txt
      house.jpg
      InfoPlist.strings
      Localizable.strings
      CitySounds/
         city1.aiff
         city2.aiff

有关语言代码和本地化资源的更多信息,请参阅国际化和本地化指南。

访问包的内容

在编写基于包的代码时,不要使用字符串常量来引用包中文件的位置。而是使用NSBundle类或CFBundleRef opaque类型来获取所需文件的路径。原因是所需文件的路径可能会根据用户的本地语言和软件包的支持的本地化而有所不同。通过让软件包确定文件的位置,您始终确保加载正确的文件。

本章介绍如何使用NSBundle类和CFBundleRef opaque类型来查找文件并获取有关您的包的其他信息。虽然这些类型不能直接互换,但它们具有相似的特征。至少在Objective-C应用程序中,您可以使用适合您需要的任何类型。

定位和开箱

在您可以访问bundle的资源之前,必须先获取一个适当的NSBundle对象或CFBundleRef opaque类型。以下部分概述了您可以获得对这些类型之一的引用的不同方法。

获得主包

主包是包含运行应用程序的代码和资源的包。如果您是应用程序开发人员,这是最常用的捆绑包。主包也是最简单的检索,因为它不需要您提供任何信息。

要获取Cocoa应用程序中的主包,请调用NSBundle类的mainBundle类方法,如清单3-1所示。

清单3-1使用Cocoa获取对主包的引用

NSBundle* mainBundle;

// Get the main bundle for the app.
mainBundle = [NSBundle mainBundle];

如果您正在编写基于C的应用程序,则可以使用CFBundleGetMainBundle函数来检索应用程序的主包,如代码清单3-2所示。

清单3-2使用Core Foundation获取对主包的引用

CFBundleRef mainBundle;

// Get the main bundle for the app
mainBundle = CFBundleGetMainBundle();

当获取主包时,确保您返回的值代表有效的包仍然是一个好主意。从任何应用程序检索主包时,在以下情况下返回的值可能为NULL:

如果程序未捆绑,尝试获取主包可能返回NULL值。捆绑代码可能会尝试创建一个主要的软件包来表示您的程序的内容,但这样做在所有情况下是不可能的。
如果启动程序的代理程序没有在argv参数中指定程序的可执行文件的完整路径,则主程序包值可能为NULL。捆绑包依赖于在argv [0]中的可执行文件的路径或PATH环境变量中可执行文件路径的存在。如果这两个都不存在,则bundle例程可能无法找到主要的bundle目录。当xinetd将当前目录更改为/时,xinetd启动的程序通常会遇到此问题。
通过路径获取捆绑
如果要访问主包以外的捆绑包,则可以创建适当的捆绑对象,如果您知道捆绑包目录的路径。在您定义框架或其他可加载捆绑包的情况下,通过路径创建捆绑包很有用,并且事先知道这些捆绑包将位于何处。

要使用Cocoa在特定路径获取bundle,请调用NSBundle类的bundleWithPath:class方法。 (您也可以使用initWithPath:instance方法初始化一个新的bundle对象。)此方法接收一个表示bundle目录的完整路径的字符串参数。清单3-3显示了访问本地目录中的bundle的示例。

清单3-3使用其路径查找可可包

NSBundle* myBundle;

// Obtain a reference to a loadable bundle.
myBundle = [NSBundle bundleWithPath:@"/Library/MyBundle.bundle"];

要使用Core Foundation在特定路径获取捆绑包,请调用CFBundleCreate函数。 在Core Foundation中指定路径位置时,必须使用CFURLRef类型。 清单3-4显示了一个示例,它将上述示例中的固定目录,将其转换为URL,并使用该URL访问捆绑包。

清单3-4使用其路径定位Core Foundation bundle

CFURLRef bundleURL;
CFBundleRef myBundle;

// Make a CFURLRef from the CFString representation of the
// bundle’s path.
bundleURL = CFURLCreateWithFileSystemPath(
                kCFAllocatorDefault,
                CFSTR("/Library/MyBundle.bundle"),
                kCFURLPOSIXPathStyle,
                true );

// Make a bundle instance using the URLRef.
myBundle = CFBundleCreate( kCFAllocatorDefault, bundleURL );

// You can release the URL now.
CFRelease( bundleURL );

// Use the bundle...

// Release the bundle when done.
CFRelease( myBundle );

在已知目录中获取软件包
即使您不知道捆绑的确切路径,您仍然可以在某个已知位置搜索它。 例如,具有PlugIns目录的应用程序可能需要获取该目录中所有bundle的列表。 一旦你有目录的路径,你可以使用适当的例程来迭代该目录并返回任何bundle。

在特定目录中查找所有bundle的最简单的方法是使用CFBundleCreateBundlesFromDirectory函数。 此函数为给定目录中的所有bundle返回新的CFBundleRef类型。 清单3-5显示了如何使用此函数来检索应用程序PlugIns目录中的所有插件。

清单3-5获取一组插件的bundle引用

CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef plugInsURL;
CFArrayRef bundleArray;

// Get the URL to the application’s PlugIns directory.
plugInsURL = CFBundleCopyBuiltInPlugInsURL(mainBundle);

// Get the bundle objects for the application’s plug-ins.
bundleArray = CFBundleCreateBundlesFromDirectory( kCFAllocatorDefault,
                    plugInsURL, NULL );

// Release the CF objects when done with them.
CFRelease( plugInsURL );
CFRelease( bundleArray );

通过标识符获取包

使用捆绑包标识符定位捆绑是查找先前加载到内存中的捆绑包的有效方式。 bundle标识符是分配给bundle的Info.plist文件中的CFBundleIdentifier键的字符串。 此字符串通常使用反向DNS符号进行格式化,以防止与其他公司的开发人员的名称空间冲突。 例如,Apple的Finder插件可能会使用字符串com.apple.Finder.MyGetInfoPlugin作为其包标识符。 不必在代码周围传递指向bundle对象的指针,而需要引用bundle的客户端可以简单地使用bundle标识符来检索它。

要使用Cocoa中的bundle标识符来检索bundle,请调用NSBundle类的bundleWithIdentifier:class方法,如清单3-6所示。

清单3-6使用Cocoa中的标识符查找包

NSBundle* myBundle = [NSBundle bundleWithIdentifier:@"com.apple.myPlugin”];

清单3-7显示了如何使用Core Foundation中的bundle标识符来检索bundle。

清单3-7使用Core Foundation中的标识符定位一个bundle
CFBundleRef requestedBundle;

 // Look for a bundle using its identifier
 requestedBundle = CFBundleGetBundleWithIdentifier(
        CFSTR("com.apple.Finder.MyGetInfoPlugIn") );

请记住,您只能使用捆绑标识符来定位已经打开的捆绑包。例如,您可以使用此技术为所有静态链接框架打开主包和捆绑包。您不能使用此技术来获取尚未加载的插件的引用。

搜索相关软件包

如果您正在编写Cocoa应用程序,可以通过调用NSBundle的allBundles和allFrameworks类方法来获取与应用程序相关的捆绑列表。这些方法创建一个NSBundle对象数组,该数组对应于应用程序当前使用的bundle或framework。您可以使用这些方法作为方便功能,而不是自己维护加载的包的集合。

bundleForClass:class方法是在Cocoa应用程序中获取相关bundle信息的另一种方法。此方法返回定义特定类的bundle。同样,这种方法大多是为了方便起见,所以你不必保留一个指向NSBundle对象的指针,这个对象只能偶尔使用。

获取参考资料包

如果您对bundle对象有引用,可以使用该对象来确定bundle中资源的位置。可可和核心基金会提供了不同的方法来定位资源。此外,您应该了解这些框架如何查找包中的资源文件,以确保在构建时将文件放在正确的位置。

捆绑搜索模式

只要您使用NSBundle对象或CFBundleRef不透明类型来定位资源,您的捆绑代码就不用担心如何从捆包中检索资源。 NSBundle和CFBundleRef都会根据可用的用户设置和捆绑配置自动检索相应的语言特定资源。但是,您仍然必须将所有这些特定于语言的资源放入您的包中,因此知道如何检索它们很重要。

捆绑编程接口遵循特定的搜索算法来定位捆绑包中的资源。全球资源最优先,其次是区域和语言专有资源。当考虑到区域和语言特定的资源时,该算法考虑了bundle的Info.plist文件中当前用户的设置和开发区域信息。

首先,包决定了整个应用程序使用哪个本地化。如果首选语言存在.lproj文件夹,则使用该本地化。否则,该包将搜索匹配下一个首选语言的.lproj文件夹,依此类推,直到找到一个。如果没有首选语言的本地化,则选择开发语言本地化。

然后捆绑包按以下顺序搜索资源:

全球(非本地化)资源
区域特定的本地化资源(基于用户的区域偏好)
针对特定语言的本地化资源(基于用户的语言偏好)
开发语言资源(由bundle的Info.plist文件中的CFBundleDevelopmentRegion指定)
要点:捆绑接口在bundle目录中搜索资源文件时会考虑到这种情况。即使在文件系统(例如HFS +)上,这种区分大小写的搜索也不会对文件名进行区分大小写。

由于全局资源优先于特定于语言的资源,所以不应同时具有给定资源的全局和本地化版本。如果存在全局版本的资源,则不会返回相同资源的特定于语言的版本。这个优先级的原因是性能。如果首先搜索了本地化的资源,那么在发现全局资源之前,捆绑例程可能会在几个本地化的资源目录中进行不必要的搜索。

iOS中的设备特定资源

在iOS 4.0及更高版本中,可以将个别资源文件标记为仅在特定类型的设备上可用。此功能简化了为通用应用程序编写的代码。而不是创建单独的代码路径来加载一个版本的资源文件的iPhone和iPad的不同版本的文件,您可以让捆绑加载例程选择正确的文件。所有你需要做的是适当地命名您的资源文件。

要将资源文件与特定设备相关联,请将自定义修饰符字符串添加到其文件名。包含此修饰符字符串会产生以下格式的文件名:

<基本名称> <设备> <filename_extension>

<basename>字符串表示资源文件的原始名称。它也代表您从代码访问文件时使用的名称。类似地,<filename_extension>字符串是用于标识文件类型的标准文件扩展名。 <device>字符串是一个区分大小写的字符串,可以是以下值之一:

〜ipad - 资源应该仅在iPad设备上加载。
〜iphone - 资源应该仅在iPhone或iPod touch设备上加载。
您可以将设备修饰符应用于任何类型的资源文件。例如,假设您有一个名为MyImage.png的图像。要为iPad和iPhone指定不同版本的图像,您将创建名称为MyImage〜ipad.png和MyImage〜iphone.png的资源文件,并将它们都包含在您的包中。要加载图像,您将继续在代码中将资源引用为MyImage.png,并让系统选择适当的版本,如下所示:

UIImage* anImage = [UIImage imageNamed:@"MyImage.png”];

在iPhone或iPod touch设备上,系统加载MyImage〜iphone.png资源文件,而在iPad上,它会加载MyImage〜ipad.png资源文件。如果找不到资源的特定于设备的版本,则系统将退回寻找具有原始文件名的资源,在上述示例中,该资源将是名为MyImage.png的映像。

获取资源的路径
为了在资源包中找到一个资源文件,您需要知道文件的名称,它的类型,或两者的组合。文件名扩展名用于标识文件的类型;因此,您的资源文件必须包含相应的扩展名。如果您使用捆绑包中的自定义子目录来组织资源文件,则可以通过提供包含所需文件的子目录的名称来加快搜索速度。

即使您没有捆绑对象,仍然可以搜索您知道路径的目录中的资源。 Core Foundation和Cocoa都提供了仅使用路径信息搜索文件的API。 (例如,在Cocoa中,您可以使用NSFileManager对象枚举目录的内容并测试是否存在文件。)但是,如果计划检索多个资源文件,则使用bundle对象总是更快。捆绑对象缓存搜索信息,因此随后的搜索通常更快。

使用Cocoa查找资源

如果您有NSBundle对象,则可以使用以下方法来查找该包中的资源位置:

* pathForResource:ofType:

* pathForResource:ofType:inDirectory:

* pathForResource:ofType:inDirectory:forLocalization:

* pathsForResourcesOfType:inDirectory:

* pathsForResourcesOfType:inDirectory:forLocalization:

假设您已经在应用程序的主包中放置了一个名为Seagull.jpg的映像。 清单3-8显示了如何从应用程序的主包中检索此映像文件的路径。

清单3-8使用NSBundle查找单个资源文件

NSBundle* myBundle = [NSBundle mainBundle];
NSString* myImage = [myBundle pathForResource:@"Seagull" ofType:@"jpg"];

如果要查找顶级资源目录中的所有映像资源,而不是仅查找单个资源,则可以使用pathsForResourcesOfType:inDirectory:方法或其等同项之一,如清单3-9所示

清单3-9使用NSBundle查找多个资源

NSBundle* myBundle = [NSBundle mainBundle];
NSArray* myImages = [myBundle pathsForResourcesOfType:@"jpg"
                              inDirectory:nil];

使用CoreFoundation查找资源

如果您有CFBundleRef不透明类型,可以使用以下方法来查找该包中的资源位置:

* CFBundleCopyResourceURL

* CFBundleCopyResourceURLInDirectory

* CFBundleCopyResourceURLsOfType

* CFBundleCopyResourceURLsOfTypeInDirectory

* CFBundleCopyResourceURLsOfTypeForLocalization

假设您已经在应用程序的主包中放置了一个名为Seagull.jpg的映像。 清单3-10显示了如何使用Core Foundation函数CFBundleCopyResourceURL通过名称和类型搜索此图像。 在这种情况下,该代码在bundle的资源目录中查找名为“Seagull”的文件,其文件类型(文件扩展名)为“jpg”。

清单3-10使用CFBundleRef查找单个资源
CFURLRef seagullURL;

    // Look for a resource in the main bundle by name and type.
    seagullURL = CFBundleCopyResourceURL( mainBundle,
                    CFSTR("Seagull"),
                    CFSTR("jpg"),
                    NULL );

假设您不想搜索一个图像文件,而是要在名为BirdImages的目录中获取所有图像文件的名称。 您可以使用函数CFBundleCopyResourceURLsOfType加载目录中的所有JPEG,如清单3-11所示。

清单3-11使用CFBundleRef查找多个资源

CFArrayRef  birdURLs;

    // Find all of the JPEG images in a given directory.
    birdURLs = CFBundleCopyResourceURLsOfType( mainBundle,
                    CFSTR("jpg"),
                    CFSTR("BirdImages") );

注意:您可以搜索没有文件扩展名的资源。要获取此类资源的路径,请指定资源的完整名称,并为资源类型指定NULL。
打开和使用资源文件
一旦你有一个资源文件的引用,你可以加载它的内容并在应用程序中使用它。加载和使用资源文件必须采取的步骤取决于资源的类型,因此本文档未涵盖。有关加载和使用资源的详细信息,请参阅资源编程指南。

查找捆绑中的其他文件

使用有效的bundle对象,您可以检索到顶级bundle目录的路径以及其许多子目录的路径。使用可用的接口来检索目录路径可以使您的代码不必了解该系统的完整结构或其位置。它还允许您在不同平台上使用相同的代码。例如,您可以使用相同的代码从iOS应用程序或具有不同捆绑结构的Mac应用程序中检索资源。

要使用Cocoa获取顶级bundle目录的路径,可以使用相应的NSBundle对象的bundlePath方法。您还可以使用builtInPlugInsPath,resourcePath,sharedFrameworksPath和sharedSupportPath方法来获取bundle的关键子目录的路径。这些方法使用NSString对象返回路径信息,您可以直接传递到大多数其他NSBundle方法或根据需要转换为NSURL对象。

Core Foundation还定义了用于检索多个不同内部捆绑包目录的功能。要获取捆绑包本身的路径,可以使用CFBundleCopyBundleURL函数。您还可以使用CFBundleCopyBuiltInPlugInsURL,CFBundleCopyResourcesDirectoryURL,CFBundleCopySharedFrameworksURL和CFBundleCopySupportFilesDirectoryURL函数来获取捆绑包的关键子目录的位置。 Core Foundation总是返回作为CFURLRef不透明类型的包路径。您可以使用此类型提取CFStringRef类型,然后可以传递给其他Core Foundation例程。

获取Bundle的Info.plist数据

每个包应该包含的一个文件是信息属性列表(Info.plist)文件。此文件是一个基于XML的文本文件,其中包含特定类型的键值对。这些键值对指定了有关捆绑包的信息,例如其ID字符串,版本号,开发区域,类型和其他重要属性。 (有关可以包含在此文件中的密钥列表,请参阅运行时配置指南。)软件包还可能包含其他类型的配置数据,大部分组织在基于XML的属性列表中。

NSBundle类提供了用于从Info.plist文件中检索信息的objectForInfoDictionaryKey和infoDictionary方法。 objectForInfoDictionaryKey:方法返回一个键的本地化值,是首选的调用方法。 infoDictionary方法从属性列表中返回一个带有所有键的NSDictionary;但是,它不会返回这些键的任何本地化值。有关更多信息,请参阅NSBundle类参考。

Core Foundation还提供从包的信息属性列表文件中检索特定数据的功能,包括软件包的ID字符串,版本和开发区域。您可以使用CFBundleGetValueForInfoDictionaryKey函数检索密钥的本地化值。您还可以使用CFBundleGetInfoDictionary检索非本地化键的整个字典。有关这些和相关功能的更多信息,请参阅CFBundle参考。

注意:由于它们考虑到本地化值,CFBundleGetValueForInfoDictionaryKey和objectForInfoDictionaryKey:是检索密钥的首选接口。
清单3-12演示了如何使用Core Foundation函数从信息属性列表中检索软件包的版本号。虽然信息属性列表中的值可以写为字符串,例如“2.1.0b7”,但该值作为无符号长整数返回。

清单3-12获取bundle的版本

// This is the ‘vers’ resource style value for 1.0.0
    #define kMyBundleVersion1 0x01008000

    UInt32  bundleVersion;

    // Look for the bundle’s version number.
    bundleVersion = CFBundleGetVersionNumber( mainBundle );

    // Check the bundle version for compatibility with the app.
    if (bundleVersion < kMyBundleVersion1)
        return (kErrorFatalBundleTooOld);

清单3-13显示了如何使用CFBundleGetInfoDictionary函数从信息属性列表中检索任意值。 生成的信息属性列表是标准Core Foundation类型CFDictionaryRef的实例。 有关从Core Foundation字典检索信息的更多信息,请参阅CFDictionary参考。

清单3-13从bundle的信息属性列表中检索信息

CFDictionaryRef bundleInfoDict;
    CFStringRef     myPropertyString;

    // Get an instance of the non-localized keys.
    bundleInfoDict = CFBundleGetInfoDictionary( myBundle );

    // If we succeeded, look for our property.
    if ( bundleInfoDict != NULL ) {
        myPropertyString = CFDictionaryGetValue( bundleInfoDict,
                    CFSTR("MyPropertyKey") );
    }

也可以获取bundle的信息字典的实例,而不需要bundle对象。为此,您可以使用Core Foundation函数CFBundleCopyInfoDictionaryInDirectory或Cocoa NSDictionary类。这可以用于搜索一组bundle的信息属性列表,而无需先创建bundle对象。

加载和卸载可执行代码

从外部程序包加载代码的关键是在bundle的可执行文件中找到一个合适的入口点。与其他插件方案一样,这需要应用程序开发人员和插件开发人员之间的一些协调。您可以发布用于捆绑包的自定义API来实现或定义正式的插件界面。在这两种情况下,一旦拥有适当的捆绑包或插件,就可以使用NSBundle类(或CFBundleRef opaque类型)来访问由外部代码实现的函数或类。

注意:直接加载Mach-O代码的另一个选项是使用NSModule加载例程。然而,这些例程通常需要更多的工作来使用,并且不如NSBundle或CFBundleRef接口那么优先。有关更多信息,请参阅OS X ABI Mach-O文件格式参考或参见NSModule手册页。
有关加载和卸载代码的其他信息,请参阅代码加载编程主题。

加载功能

如果您在C,C ++,甚至Objective-C中工作,您可以将接口发布为一组基于C的符号,例如函数指针和全局变量。使用Core Foundation功能,可以从bundle的可执行文件加载对这些符号的引用。

您可以使用以下几种功能检索符号。要检索函数指针,请调用CFBundleGetFunctionPointerForName或CFBundleGetFunctionPointersForNames。要检索一个指向全局变量的指针,请调用CFBundleGetDataPointerForName或CFBundleGetDataPointersForNames。例如,假设可加载的bundle定义了清单3-14所示的函数。

清单3-14可加载bundle的示例函数

// Add one to the incoming value and return it.
    long addOne(short number)
    {
        return ( (long)number + 1 );
    }

给定一个CFBundleRef不透明类型,您将需要搜索所需的函数,然后才能在代码中使用它。 清单3-15显示了一个说明此过程的代码片段。 在本示例中,myBundle变量是指向该bundle的CFBundleRef不透明类型。

清单3-15在可加载的bundle中查找一个函数

// Function pointer.
AddOneFunctionPtr addOne = NULL;

// Value returned from the loaded function.
long result = 0;

// Get a pointer to the function.
addOne = (void*)CFBundleGetFunctionPointerForName(
            myBundle, CFSTR("addOne") );

    // If the function was found, call it with a test value.
if (addOne)
{
    // This should add 1 to whatever was passed in
    result = addOne ( 23 );
}

加载Objective-C类
如果您正在编写一个Cocoa应用程序,您可以使用NSBundle的方法加载整个类的代码。用于加载类的NSBundle方法仅适用于Objective-C类,不能用于加载用C ++或其他面向对象语言编写的类。

如果可加载的bundle定义了一个主体类,可以使用NSBundle的principalClass方法加载它。 principalClass方法使用bundle的Info.plist文件的NSPrincipalClass键来标识和加载所需的类。使用主体类减轻了对外部类的特定命名约定的一致意见,而不是让您专注于这些接口的行为。例如,您可以使用主体类的实例作为工厂来创建其他相关对象。

如果要从可加载的bundle加载任意类,请调用NSBundle的classNamed:方法。此方法搜索包中与您指定的名称匹配的类。如果类存在于bundle中,则该方法返回相应的Class对象,然后可以使用该对象来创建该类的实例。

清单3-16显示了一个用于加载bundle的主类的示例方法。

清单3-16加载bundle的主类

- (void)loadBundle:(NSString*)bundlePath
{
    Class exampleClass;
    id newInstance;
    NSBundle *bundleToLoad = [NSBundle bundleWithPath:bundlePath];
    if (exampleClass = [bundleToLoad principalClass])
    {
        newInstance = [[exampleClass alloc] init];
        // [newInstance doSomething];
    }
}

有关NSBundle类的方法的更多信息,请参阅NSBundle类参考。

卸载捆绑

在macOS v10.5及更高版本中,您可以使用卸载方法卸载与NSBundle对象关联的代码。您可以使用此技术通过删除不再需要的插件或其他可加载的软件包来释放应用程序中的内存。

如果您使用Core Foundation加载捆绑包,则可以使用CFBundleUnloadExecutable函数来卸载它。如果您的软件包可能被卸载,则需要确保通过设置适当的编译器标志来正确处理字符串常量。

编译包含最小部署目标为macOS v10.2(或更高版本)的软件包时,编译器会自动切换到响应于CFSTR(“...”)的真正恒定的生成字符串。如果使用标志-fconstant-cfstrings进行编译,编译器也会生成这些常量字符串。常量字符串有很多好处,应尽可能使用,但是如果在包含它们的可执行文件被卸载后引用常量字符串,引用将无效并将导致崩溃。即使字符串已被保留,例如,由于放入数据结构,直接保留,甚至在某些情况下甚至被复制,这可能会发生。而不是试图确保在卸载时清除所有这些引用(并且一些引用可能在库中创建,使其难以跟踪),最好使用标志-fno-constant-cfstrings来编译无法加载的bundle。

文件包

如果您的文档文件格式由于几种不同类型的数据而变得太复杂,您可能会考虑为文档采用包格式。文档包给用户提供单个文档的错觉,但为您提供如何在内部存储文档数据的灵活性。特别是如果您使用几种不同类型的标准数据格式,例如JPEG,GIF或XML,则文档包使访问和管理数据变得更加容易。

定义您的文档目录结构

苹果公司没有为文件包规定任何特定的结构。包目录的内容和组织由您自己承担。但是,鼓励创建一个平面目录结构或使用框架绑定结构,这涉及将文件放在一个顶级的Resources子目录中。 (有关框架的捆绑结构的更多信息,请参阅框架包。)

注册您的文档类型

要将文档注册为包,必须在应用程序的信息属性列表(Info.plist)文件中修改文档类型信息。 CFBundleDocumentTypes密钥存储有关应用程序支持的文档类型的信息。对于每个文档包类型,请包含具有适当值的LSTypeIsPackage密钥。该键的存在告诉Finder和Launch Services将具有给定文件扩展名的目录作为一个包来处理。有关Info.plist键的更多信息,请参阅信息属性列表键参考。

文档包应该始终有一个扩展名来标识它们 - 即使该扩展名可能被用户隐藏。该扩展允许Finder识别您的文档目录并将其视为一个包。您不应该将文档包与MIME类型或4字节操作系统类型相关联。

创建新文档包

当您的应用程序创建新文档时,可以通过以下两种方法之一:

使用NSFileWrapper对象创建文档包。
手动创建文档包。
如果您正在创建一个Cocoa应用程序,则NSFileWrapper类是创建文档包的首选方式,因为它与NSDocument中现有的支持关联以读取和编写文档内容。 (NSFileWrapper类在iOS 4.0及更高版本中也可用作Foundation框架的一部分。)有关如何使用此类的信息,请参阅NSFileWrapper类参考。

如果您正在编写不使用更高级别Objective-C框架(例如AppKit或UIKit)的命令行工具或其他类型的应用程序,那么您仍然可以手动创建文档包。关于创建文档包的重要事情是它只是一个目录。只要文档包的类型被注册(如注册您的文档类型中所述),您所要做的就是创建一个具有相应文件扩展名的目录。 (Finder使用文件扩展名作为它的提示来将目录视为一个包。)您可以使用标准的BSD文件系统例程或使用NSFileManager类创建目录(并创建要放入该目录的任何文件)基金会框架。

访问您的文档内容

有几种方法来访问文档包的内容。由于文档包是目录,您可以使用任何适当的文件系统例程来访问文档的内容。如果您为文档包使用bundle结构,还可以使用NSBundle或CFBundleRef例程。捆绑结构的使用对于存储多个本地化的文档尤其适用。

如果您的文档包使用平面目录结构或包含一组固定的内容文件,则可能会发现使用文件系统例程比使用NSBundle或CFBundleRef更快更容易。但是,如果文档的内容波动,则应考虑使用捆绑结构和NSBundle或CFBundleRef来简化文档中文件的动态发现。

如果您正在创建Cocoa应用程序,还必须记住自定义NSDocument子类加载文档包的内容的方式。使用readFromData:ofType:error:和dataOfType:error:读取和写入数据的方法的传统技术仅适用于单个文件文档。要处理文档包,必须使用readFromFileWrapper:ofType:error:和fileWrapperOfType:error:methods。有关从NSDocument子类读取和写入文档数据的信息,请参阅基于文档的应用程序概述。

词汇表

bundle包含词汇表的目录,其目录以系统识别的多种方式之一组织。

显示名称显示用户可见字符串,代替项目的实际名称。显示名称允许用户定制关键项目(如应用程序)的名称,而不会破坏依赖于原始名称的系统部分。

框架包含动态共享库和头文件等资源的bundle结构来支持该库。

信息属性列表包含捆绑包的配置信息的特定类型的属性列表。信息属性列表文件必须始终具有名称Info.plist。有关更多信息,请参阅运行时配置指南。

可加载捆绑包可执行文件被设计为由应用程序动态加载到内存中的捆绑包。可装入的捆绑包有时也称为插件。

软件包Finder提供给用户的目录,就像它是单个文件一样。

目标用于创建产品的Xcode蓝图。目标定义了用于编译源文件,复制资源文件以及执行构建产生的产品所需的任何其他步骤的规则。

版本捆绑包支持包含多个版本的可执行文件和资源的捆绑包。框架是唯一支持版本的捆绑包。

推荐阅读更多精彩内容

  • 介绍 捆绑是macOS和iOS中用于封装代码和资源的基础技术。软件包通过为所需资源提供已知位置来简化开发人员体验,...
    nicedayCoco阅读 317评论 0 0
  • 关于资源 适用于计算机程序的资源是与程序可执行代码相关的数据文件。资源可以通过将代码之外的复杂数据集或图形内容创建...
    nicedayCoco阅读 104评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 70,490评论 12 116
  • 关于首选项和设置 首选项是您持久存储的信息,并用于配置您的应用程序。应用程序通常会向用户公开偏好设置,以便他们自定...
    nicedayCoco阅读 241评论 0 0
  • 字符串资源 本地化过程的一个重要部分是本地化应用程序显示的所有文本字符串。根据其性质,位于nib文件中的字符串可以...
    nicedayCoco阅读 59评论 0 0