框架(framework)是一种类型的束(bundle),用于打包共享资源,例如:动态共享库、资源文件、头文件和参考文献等。这些资源的组合同时也给框架带来许多好处。举例来说,它使得动态共享库对资源的定位更加容易,也使得用户的安装和卸载操作更加方便。
框架束具有.framework的扩展名。在束的内部,可能存有框架的多个主要版本。位于框架文件夹顶层的符号链接指向库代码和资源的最新版本。动态链接编辑器把框架安装的目录地址写入可执行框架中。在运行一个程序时,如果动态链接编辑器在这个地址无法找到一个框架,它就会到标准目录地址里寻找。系统和第三方框架常常被安装在标准目录地址里。第三方框架可能也被包含在需要使用这些框架的应用程序包里。
框架中的可执行代码是一个动态共享库。多重、并发运行中的程序可以在这个库中共享代码,而无需它们自身的代码拷贝。不同于静态链接的共享库,一个程序中的未定义符号与其所链接的动态共享库,被延迟到该程序执行时才被绑定。当程序引用这些未定义符号时,动态链接编辑器试图在运行时解决这些未定义符号。如果一个库模块中的符号没有被程序所引用,那么该模块就不会被链接。动态共享库的安装路径被写入到所有用那些库文件来创建的可执行程序中。
框架可以具有主要版本(或称为不兼容版本)和次要版本(或称为兼容版本)。主要版本方案提供了向后兼容性。如果框架不能兼容于同老版本库文件相链接的程序,这时它就需要被赋予一个新的主要版本。而上面所说的这些程序必须与保留在框架束内部的一个早期版本相链接。次要版本方案提供了向前兼容性。一个框架的主要版本可以合并许多个次要版本。一个次要版本所体现的是与最新构建的框架相链接的程序的框架兼容性。
注意:在Mac OS X的最新版本中,框架都是“版本化”的束,它的内部目录结构缺少了许多出现在应用程序、可加载束和更新类型的束中的特性。而对于“新格式”类型束的描述,请参见“束”一章中的“束的剖析”。
当在一些计算环境中安装库文件时,它们会被放置在文件系统中的某一位置中,而与代码相关联的资源则被安装到了其他位置。这些相关联的资源包括头文件,和图像、本地化字符串等这类资源。这种代码和资源的分散放置可能会产生以下几种问题:
- 它使得库文件及其资源的卸载变得很复杂。
- 它加大了在库文件和头文件之间错配所带来的风险。
- 它使得库文件代码对资源的定位更加困难。
框架通过将动态共享库和资源绑定在一起来解决这些问题,所绑定的这些资源是库文件(及其所关联代码)需要用到的。的确,“束”是一个合适的词,因为框架是一种与应用程序及插件一样的束。然而,框架在某些重要方式上还是不同于其它类型的束。
- 框架含有独特的资源类型--头文件。当然,它们也可以含有资源中其他特定的内容,例如:私有的静态库。
- 在建立一个框架时,并没有设置束位(bundle bit),所以Finder没有把框架作为文件包(一个以文件方式来展现的目录), 因此开发人员可以浏览这些被打包的头文件。
- 框架是版本化的束,有关这些内容在“框架的内部结构” 中有详细描述。
版本化的束所具有的内部结构来源于Mac OS X Server(和更先前操作系统)中的束。苹果最终会将框架转换成新的内部结构。到那时,Mac OS X将支持束的两种格式;束的系统例程既可以处理“版本化”的束,也可以处理“新格式”的束。
Mac OS X中的框架是一种具有.framework扩展名的目录。当您打开目录时,它的目录第一级看上去就像是这样:
GreatSoftware.framework/ GreatSoftware Headers/ PrivateHeaders/ Resources/ Versions/在这个例子中,GreatSoftware表示的是动态共享库。Headers和PrivateHeaders是用于存放框架的公共和私有头文件的子目录。框架的资源--例如:接口定义文件、图像、声音和本地化字符串都放置在Resources子目录下。
Versions子目录在这一级上是唯一“真实”的目录。GreatSoftware、Headers、 PrivateHeaders和Resources都是与库文件及框架的当前主要版本目录的符号链接(类似于替身)。图7-1描述了这种链接是如何进行的。
图 7-1 框架的目录结构
![]()
框架的目录可以包含动态共享库的多个主要版本(同时也可以包含有它们的资源)。当动态共享库与那些和早期版本的库文件相链接的程序不兼容时,典型地,这时就需要有一个新的主要版本。这些应用程序不但可以运行该共享库的最新版本,而且也可以运行那些包含在该框架束中的老版本。框架中的每一个版本都被放置在指定的Versions子目录中,按照惯例,它们按照字母表的顺序排列。关于框架的主要版本和次要版本及通用版本的更多内容,请参见“框架的版本化”。
框架中的Resources目录的内部结构与“新格式”束中的目录结构相似。本地化资源都被放置在Resources子目录下。每一个子目录都有一个名字作为语言的标志(也可能是一个区域名,在该区域使用了这种语言)并用.lproj作为扩展名。对于各个使用地方性语言的区域,其专有资源名称必须从ISO 3166标准国家代码以及ISO 639标准语言代码中取得(用有下划线来分割这二个代码)。举例来说,在加拿大使用的法语其专有资源目录应命名为fr_CA.lproj。但如果您想设置一个可以存放所有法语方言的目录地址,那么它的名称应设为French.lproj。带有.lproj扩展名的目录下存放有本地化的字符串、图像、声音和接口定义。框架与“新格式”束的重要不同在于框架中的非本地化资源并没有位于Resources的Nonlocalized Resources子目录下,而是被放在Resources的第一级目录下。
系统框架是由苹果公司提供的框架,例如Application Kit或QuickTime框架。系统框架中的共享库代码被特意设计来为系统中的全部应用程序所使用。系统框架安装在/System/Library/Frameworks目录下。依赖于某些特定因素,第三方框架也能被安装在不同的文件系统位置里。
- 如果只有单用户使用这种框架,那么它们就必须安装在用户个人(home)目录的Library/Frameworks子目录下。
- 如果一个特定Mac OS X系统中的所有用户都需要使用这种框架,那么它们就必须安装在/Library/Frameworks目录下。
- 如果需要通过局域网来使用这些框架,那么它们就必须安装在/Network/Library/Frameworks目录下。
当您在建立一个应用程序或其它可执行程序时,编译器会在/System/Library/Frameworks目录以及任何一个向编译器指明的目录下寻找所需导入的框架。所期望需要装载框架的路径连同版本信息一起被写入可执行程序中。当应用程序在运行时,动态链接编辑器首先尝试链接那些路径已被写在可执行程序中的框架。如果编译器在指定位置上没有找到该框架的话(可能该框架已经被挪动或删除了),那么它就会按以下排列顺序在这些标准后备位置中寻找框架:
-/Library/Frameworks /Library/Frameworks /Network/Library/Frameworks /System/Library/Frameworks如果动态链接编辑器无法对所需要的框架进行定位,那么它通常会出现链接错误,同时应用程序也将无法启动运行。
框架束中的可执行代码是一种动态链接的共享库--或者简单的称为动态共享库。这种库的代码可以被多重和并发运行的程序所共享使用。程序精确地共享使用库代码的物理副本,而无需程序本身的代码拷贝。动态共享库带来了几个好处,它们能够高效地使用内存,并允许开发人员在库代码中修正bug和测试这些修正结果,而不必重新编译需要使用这些代码库的应用程序。
注意:尽管您能创建一个驻留在框架外的动态共享库,但这是一种很不常见的方法。按照习惯,单独存在的动态共享库以.dylib为扩展名,并且典型地被安装在用于放置库文件的标准文件系统位置中。
动态共享库的特色是它们可以与静态链接的共享库分离开来。在使用动态共享库时,一个程序中的未定义符号与其所链接的动态共享库,被延迟到该程序执行时才被绑定。换句话来说,动态链接编辑器不但试图在运行时解决程序中所有未定义的符号,而且更试图在程序执行期间,只在这些符号被引用时才进行这些工作。如果一个未定义符号没有被引用,那么这一特定程序在执行时也就不需要这种绑定。
这种动态动作源于动态共享库的组成方式。动态共享库由落干对象化的代码模块构成,在动态共享库中,代码模块保持着它们各自的“界线”;那就是说,源程序模块中的代码没有被合并到单一的代码基础上。当一个链接着动态共享库的程序被启动后,动态编辑器会自动加载和链接库中的模块,但它只在需要时才会对这些模块进行链接;换句话来说,也就是仅仅在符号被引用以及定义在模块中的函数被调用时,该模块才会被链接。如果模块中的代码没有被引用或调用,那么该模块就不会被链接。图7-2举例描述了这种“惰性链接(Lazy linking)”动作。在这个例子中,当库函数a被调用时,模块a.o就被链接到程序的主例程中;当程序函数doThat中的库函数b被调用时,模块b.o也就被链接上;而模块c.o从未被链接,因为它从来都没有被调用过。
图 7-2 动态共享库模型的惰性链接
作为框架开发人员,在设计您的动态共享库时,应该采用这种对分离的代码模块进行按需链接的设计思想。动态链接编辑器总是试图首先在同一个模块内对未解决的符号进行捆绑,而后才是在其他模块和库内。所以您应该确保把那些相互依赖的代码放置在一个模块中。例如,用户定义了用于分配资源和释放资源的例程,它们应该被放入同一个模块内。这种技术可以防止使用错误的符号定义。当一种符号定义存在于多个动态共享库中时,这种错误就会发生,并且其它的符号定义会覆盖正确的符号定义。
当您在创建一个框架时,就必须确保在库中的每一个符号都仅仅被定义了一次。另外,在库中“公共(common)”符号是不能被允许使用的;在C代码中,您必须使用一个单一的真实的定义,并优先于其他所有外部关键词的定义。
当您创建一个依赖于动态共享库的程序时,库文件的安装路径被记录在程序中。对于苹果公司所提供的系统框架来说,其安装路径是绝对的。而对于第三方框架来说,其安装路径取决于包含有框架的应用程序包。这种获得库文件安装路径的方法提高了程序的启动运行性能。取代了以往在程序启动时,不得不对文件系统进行搜索的方法,现在动态链接编辑器会直接找到动态共享库,并将其链接到程序中。这就清楚地表明,要想运行一个程序,任何一个所需要的库都必须安装在所记录路径指示的位置上,或者安装在框架和库的一个标准后备位置中。
某些动态共享库也可以与其它的动态共享库有着从属关系,同时这些从属关系被记录在可执行的库文件中。当动态链接编辑器为一个应用程序链接其所依赖的第一层动态共享库时,它可以获得那些与其层层相互从属的所有库文件的路径,并对它们进行链接。因此,当用户在程序中使用并链接一个动态链接库时,用户无需清楚地了解这种库与库之间的依赖关系。
动态共享库也可以是版本化的,以达到向后兼容和某种程度上的向前兼容的目的。关于该主题的更多内容请参见“框架的版本化”一节。
您可以为框架创建不同的版本,而考虑到使动态共享库产生变化的类型不同,框架具备了两种版本类型:主要版本(或称为非兼容版本)和次要版本(或称为兼容版本)。
框架的“主要版本”也被称为“非兼容版本”。就主要版本而言,那些和框架的动态共享库的早期版本相链接的程序与框架的当前版本不兼容。如果任何一个这种程序试图依靠在新版本的框架上运行,那么它就很可能会遇到运行时错误。
因为框架所有的主要版本通常都保留在框架束中,所以这种程序尽管与当前版本不兼容,但它仍然可以依赖于所兼容的版本,使其继续得以运行。框架的每一个主要版本的路径都被编码成版本名(参见“框架的内部结构)。例如,在如下所示的路径中,字母“A”表示的是假定框架中的主要版本的版本名:/System/Library/Frameworks/Boffo.framework/Versions/A/Boffo
在程序建立时,这条路径被记录到可执行程序自身的代码中。当程序运行时,动态链接编辑器利用所记录的这条路径找到框架的库文件的可兼容版本。由于框架中包含其所有的主要版本,并在可执行程序中记录下每一个所依赖的框架的主要版本,因此对主要版本的描述策略能够使得框架具备向后兼容性。
当以下所叙述的任何一种变化引发动态共享库与链接着该库早期版本的程序不兼容时,您就必须为框架制作一个新的主要版本。这些变化主要有:
- 公共API的移动,例如,类、函数、方法或结构
- 公共API的重命名
- 数据结构的布局变化,或者是类的实例变量的增加、修改或重排
- 为一个C++类添加方法
- 改变公共API的可编程接口
对于最后一类变化,例如可以是改变函数中参数的顺序。
大多数近期生成的框架其主要版本一般都被做成“当前(current)”版本的形式。除非您有特别说明,否则您编写的每一个程序都与库文件的当前版本相链接;您所重新编写的老程序也将与当前版本相链接。在构造框架时,系统自动生成一系列符号链接,将其指向框架的当前主要版本。详情请参见“框架的内部结构”。
在您创建一个框架的新的主要版本时,您的集成开发环境为您照顾到大部分的实现细节。您所需要做的仅仅是指定主要版本的标识符。按照习惯,这种标识符常用字母表中的字母来表示,每一个新版本的标识符都在早期版本的基础上“增加”一级。尽管如此,您也可以按照您自己的习惯来命名标识符,例如“2.0”或“Two”。
您也可以为单独存在的动态共享库制作不兼容的主要版本(即库文件没有被包含在框架束中)。这种类型的库的主要版本被编码在它自身的文件名中,例如:libMyLib.B.dylib
这时,假设该库文件是最新的主要版本,并创建指向它的符号链接libMyLib.dylib,这样就生成了动态共享库的当前主要版本。
在框架的一个主要版本内,还可以存在有一系列的次要版本(或兼容版本)。在框架的同一主要版本下,框架的次要版本决定了它与链接着最新创建框架的程序的兼容性。次要版本的描述策略因此有助于向前兼容性的建立。如果一个程序接口被引入框架的最新版本,那么依赖于该框架所建立的程序将有可能无法依靠该框架的早期次要版本来得以运行。由于程序有可能引用了那些新的API,因此,如果一旦启动这些程序,它将有可能导致链接编辑错误(link-edit error)。次要版本可以使框架开发人员对框架的新旧程度进行控制。对于那些链接着框架的较新版本的可执行程序, 它可以用于明确该框架的哪些老版本能够与其保持兼容。
当前版本与兼容性版本,这两种版本号之间的关系指示了与一个特定程序相关的框架的次要版本的地位。在每一次兼容性发生变化后,框架就被重建,其当前版本号也随之递增(这就是说,变化不需要一个新的主要版本)。
框架中变化的类型影响着第二个次要版本号(即兼容性版本)。如果那些变化仅仅是修正bug或是不影响任何公共API的改进,那么兼容性版本就会保留它的当前版本号而不做任何变动。然而,如果您已经给框架添加了类、方法、函数、结构或任何其它公共API,那么兼容性版本号就必须设置成与当前版本号一样的值。
注意
对Objective-C或C++类的实例变量的添加或对C++方法的添加构成了主要非兼容变化,而不是次要兼容变化。
当新建或重建一个框架时,框架的当前版本号和它的兼容性版本号都被记录在框架的动态共享库中。当您建立了一个依赖于链接该框架的程序时,这些同样的版本号连同框架路径一起被编码在可执行程序中(这些框架含有主要版本标识符)。在您试图运行一个程序时,动态链接编辑器对程序的兼容版本号和框架的兼容版本号进行比较。如果程序的兼容性版本的级别比框架的兼容性版本级别高,那么该程序就不能被执行。
次要版本的描述策略不但被应用在框架中,同样也被应用在单独存在的动态链接库中。
在Mac OS X中,对框架和动态共享库而言有两种类型的版本:
- 主要非兼容版本--是指一种和特定程序不兼容的框架,这种特定程序与该框架的动态链接库的早期版本相链接(主要非兼容版本具有向后兼容性)。
- 次要兼容版本--是指一种和特定程序相兼容的框架,这种特定程序与同一主要版本内的最新建立的框架相链接(次要兼容版本具有向前兼容性)。
表7-1总结了每种版本类型的突出特性
表7-1框架版本化的总结
版本的类型 版本创建所需的条件 版本创建过程中发生的变化 主要/非兼容版本
(具有向后兼容性)API 发生变化(例如,对函数重命名);删除 API;新建的或重排实例变量;新建 C++ 方法。 主要版本的标示符发生变化;新的标示符影响到框架中的路径。动态共享库的路径被记录到需要链接该框架的程序中。 次要/兼容版本
(具有向前兼容性)新建函数、方法、类、结构等等。 当前(次要)版本的版本号被提升;兼容性版本的版本号被赋予与当前(次要)版本相同的版本号。这个版本号被记录到需要链接该框架的程序中。
无 bug 修正,增强性能,但不影响编程接口。 当前(次要)版本的版本号被提升;兼容性版本号保持不变。这个版本号被记录到需要链接该框架的程序中。
命令行程序otool的输出结果可以让您明白版本信息是如何被记录在一个可执行程序中的。在使用这个程序时,可进入任何Mac OS X应用程序的目录中,并在终端的shell中键入下列命令: otool -L appName 这里appName是指应用程序的名字。
在您需要改变框架的主要版本号而又没有改变它时,与其相链接的程序将会在无法预料到的情况下不能运行。但如果在您无需改变主要版本号而又改变了它时,您将因为不需要的框架而把系统弄得混乱。
为了避免不得已的版本号改动,有如下主要对策:
- 给类和结构添加保留字段。无论何时当您给公共类添加一个实例变量时,您必须更改主要版本的版本号,因为子类取决于超类的大小。尽管如此,您可以给类和结构添加未使用的("保留的")实例变量和字段的定义。然后,如果您需要给类添加实例变量,那么您可以改为定义一个具有您所需要存储量的完全新的类,同时要使您所保留的实例变量指向这个所定义的新类。
切记:添加那些需要频繁使用的类的实例变量,或者添加那些需要频繁分配的数据结构的字段都会占用内存。
- 不要公布API,除非您希望您的用户能够使用这些API。您可以自由的改变私有的API,只要您能确保此时没有程序正在使用它。在一个私有头文件中声明任何一个API都是极其危险的。
- 不要删除旧有的程序接口。如果一个方法或函数不再需要被执行,那么把它留在API中可以达到兼容的目的。但要确定由它所返回的值应该是恰当合理的。即使您给方法或函数添加了一些附加参数,但如果完全可能的话,应仍然保留下其旧有的形式。
- 记住如果您是添加API而不是更改或删除它,那么您无需非要去更改主要版本的版本号,因为老的API仍然存在。不过,这个规则并不适用于实例变量。(然而,您必须更改兼容性版本的版本号。)
当您在集成开发环境里进行“make clean”操作时,您会删除工程目录中的整个框架束,这就意味着除了删除了当前的二进制文件外,也删除了其它旧有的二进制文件。而随后唯一能够生成的只是当前版本。在这种情况下您没有办法恢复到早期版本。如果您不得不进行清空操作,那么您就需要为工程创建多个复本:一个用于建立当前版本,其它的用于建立早期版本。
[ 返回 ]