本文翻译自:Framework Programming Guide(更新日期:2013-09-17
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html#//apple_ref/doc/uid/10000183i
文章目录
- 一、框架编程指南简介
- 本文档的组织
- 二、什么是框架(Frameworks)?
- 三、Framework Bundles 剖析
- 1、框架包结构
- 框架版本
- 附加目录
- 框架配置
- 2、伞形框架束结构
- 雨伞框架的目的
- Umbrella框架包
- 四、框架版本
- 1、主要版本
- 1.1 主要版本编号方案
- 1.2 何时使用主要版本
- 1.3 避免主要版本变更
- 1.4 创建框架的主要版本
- 2、次要版本
- 2.1 次要版本编号方案
- 2.2 何时使用次要版本
- 2.3 运行时兼容版本号
- 2.4 创建框架的次要版本
- 3、版本控制指南
- 五、框架和绑定
- 1、动态共享库
- 符号绑定
- 组织你的框架代码
- 库依赖项
- 独立动态共享库
- 2、框架和预绑定
- 预绑定您的框架
- 预装订注意事项
- 查找框架的首选地址
- Apple 框架和预绑定
- 六、框架和弱链接
- 1、弱链接和 Apple 框架
- 2、弱链接标记符号
- 3、使用弱链接符号
- 4、与整个框架的弱链接
- 七、创建框架的指南
- 1、API 命名指南
- 2、框架的性能影响
- 3、你的框架中应该包含哪些内容
- 4、在框架代码中使用 C++
- 5、不要创建伞状框架
- 6、在哪里安装框架
- 八、创建框架
- 1、创建你的框架
- 配置你的框架项目
- 现场测试你的框架
- 2、在应用程序包中嵌入私有框架
- 对两个目标使用一个 Xcode 项目
- 为每个目标使用单独的 Xcode 项目
- 3、构建框架的多个版本
- 更新次要版本
- 更新主要版本
- 九、在运行时初始化框架
- 1、初始化例程和性能
- 2、定义模块初始化器和终结器
- 3、使用初始化例程
- 十、导出框架接口
- 1、创建你的导出文件
- 2、指定您的 Exports 文件
- 十一、安装框架
- 1、公共框架的位置
- 私有框架的位置
- 3、安装框架
- 十二、引入框架
- 1、在你的项目中包含框架
- 2、在非标准目录中定位框架
- 3、标题和性能
- 4、子框架链接的限制
一、框架编程指南简介
OS X 广泛使用框架来分发共享代码和资源,例如系统本身的接口。
您可以创建自己的框架,为公司的一个或多个应用程序提供共享代码和资源。
您还可以创建包含类库或附加模块的框架,以便将它们分发给其他开发人员。
本文档中的信息提供了创建框架所需的背景知识以及在 Xcode 中创建框架所需的步骤。
虽然创建框架并不困难,但在创建框架时仍需遵循一些指导原则。
Xcode 通过帮助您创建框架包并管理该包中文件的信息和位置来简化创建过程。
但是,本文档还提供了有关如何执行许多不太明显的任务的其他信息。
本文档的组织
本文档包含以下文章:
- 什么是框架?提供有关框架是什么以及如何使用框架的背景信息。
- 框架包的解剖描述了框架的基本结构,包括总括框架。
- 框架版本描述了用于管理不同框架版本的系统以及如何在创建框架时指定版本信息。
- 框架和绑定解释了框架符号如何在运行时绑定到应用程序。
它还解释了如何通过使用预绑定来缩短框架的加载时间。 - 框架和弱链接解释了框架符号的“弱链接”概念,并向您展示了如何在您自己的框架和第三方框架中使用此功能。
- 创建框架指南提供了用于创建框架的最佳实践的指南。
- 创建框架展示了如何使用 Xcode 创建公共框架和私有嵌入式框架。
- 在运行时初始化框架展示了如何为框架创建加载时初始化例程。
- 导出您的框架界面展示了如何将框架导出的符号限制为您想要的精确集合。
- 安装您的框架解释了在何处安装自定义框架的约定。
- 包括框架展示了在应用程序中使用框架的基本方法。
二、什么是框架(Frameworks)?
框架是一个分层目录,它将共享资源(例如动态共享库、nib 文件、图像文件、本地化字符串、头文件和参考文档)封装在一个包中。
多个应用程序可以同时使用所有这些资源。
系统会根据需要将它们加载到内存中,并尽可能在所有应用程序之间共享资源的一个副本。
框架也是一个包,可以使用 Core Foundation Bundle Services 或 Cocoa NSBundle 类访问其内容。
但是,与大多数包不同,框架包不会在 Finder 中显示为不透明文件。
框架包是用户可以浏览的标准目录。
这使开发人员可以更轻松地浏览框架内容并查看任何包含的文档和头文件。
框架的作用与静态和动态共享库相同,即它们提供可由应用程序调用以执行特定任务的例程库。
例如,Application Kit 和 Foundation 框架为 Cocoa 类和方法提供了编程接口。
与静态链接库和其他类型的动态共享库相比,框架具有以下优势:
- 框架将相关但独立的资源分组在一起。
这种分组使安装、卸载和定位这些资源变得更加容易。 - 框架可以包含比库更广泛的资源类型。
例如,框架可以包含任何相关的头文件和文档。 - 同一个包中可以包含多个版本的框架。
这样就可以向后兼容旧程序。 - 无论有多少进程正在使用这些资源,框架的只读资源在任何时候都只能物理地驻留在内存中。
这种资源共享减少了系统的内存占用并有助于提高性能。
注意: 框架不需要提供编程接口,并且仅可以包含资源文件。但是,这种用法并不常见。
Darwin 层包含许多静态和动态库,但除此之外,大多数 OS X 接口都打包为框架。
一些关键框架(包括 Carbon、Cocoa、Application Services 和 Core Services)提供了几个较小但相关的框架的便捷分组。
这些框架组称为伞形框架,它们充当技术与实现该技术的子框架之间的抽象层。
除了使用系统框架外,您还可以创建自己的框架,并将其私下用于您自己的应用程序或公开提供给其他开发人员。
私有框架适用于您想在自己的应用程序中使用但不想让其他开发人员使用的代码模块。
公共框架旨在供其他开发人员使用,通常包括定义框架公共接口的标头和文档。
三、Framework Bundles 剖析
在 OS X 中,共享资源使用标准框架和伞形框架进行打包。
这两种框架都具有相同的基本结构,并且可以包含 shared library、nib 文件、图像文件、字符串文件、信息属性列表、文档、头文件等资源。
伞形框架对标准框架结构进行了细微的改进,例如能够包含其他框架。
框架以包结构打包。
框架包目录以.framework
扩展名结尾,与大多数其他包类型不同,框架包以目录而不是文件的形式呈现给用户。
这种开放性使开发人员可以轻松浏览框架中包含的任何头文件和文档。
1、框架包结构
框架包使用的包结构与应用程序使用的包结构不同。
框架的结构基于早期的包格式,允许在包内存储框架代码和头文件的多个版本。
这种类型的包称为版本化包。
支持框架的多个版本可让较旧的应用程序继续运行,即使框架二进制文件不断发展。
系统通过目录名称上的.framework
扩展名 和 框架包顶层的Resources
目录来识别框架。Resources
目录中是包含包识别信息的Info.plist
文件。
实际的Resources
目录不必物理上驻留在包的顶层。
事实上,OS X附带的系统框架在此位置有一个到框架Resources
目录的符号链接。该链接指向隐藏在包内某处的最新版本的Resources
目录。
[Resources
]目录的内容与应用程序包的内容相似(有关详细信息,请参阅 *Bundle编程指南*中的“现代Bundle剖析”。)
本地化资源放在以.lproj
扩展名结尾的特定语言子目录中。
这些子目录保存本地化到目录所表示的语言和区域的字符串、图像、声音和接口定义。
非本地化资源位于Resources
目录的顶层。
框架版本
当您在 Xcode 中构建新的框架项目时,构建环境会自动为您创建一个版本化的包结构。
例 1显示了生成的包的基本目录结构。
例 1 一个简单的框架包
MyFramework.framework/
MyFramework -> Versions/Current/MyFramework
Resources -> Versions/Current/Resources
Versions/
A/
MyFramework
Resources/
English.lproj/
InfoPlist.strings
Info.plist
Current -> A
在这个清单中,Versions
目录是包顶层唯一的真实目录。MyFramework
和Resources
都是指向Versions/A
中项目的符号链接。符号链接的原因是目录Versions/A
包含框架的实际内容。它包含可执行文件和框架使用的资源。
清单1显示顶层符号链接并不直接指向Versions/A
目录中的项目,而是指向Versions/Current
目录中的项目,该目录本身就是指向Versions/A
的符号链接。这种额外的间接级别简化了更改具有许多资源类型的框架的顶层链接以指向框架的特定主要版本的过程,因为只需要更新一个链接Versions/Current
。
每个Versions
中的真实目录都包含框架的特定主要版本。当这些版本是当前版本时创建的客户端需要框架的早期主要版本。只有当框架的动态共享库的更改会阻止与其链接的程序运行时,才需要框架的新主要版本。使用该库的早期主要版本构建的程序必须继续使用它,但开发中的程序应链接到该库的当前版本。
例2显示了MyFramework框架在添加了重大修订后。
例 2 具有多个版本的框架
MyFramework.framework/
MyFramework -> Versions/Current/MyFramework
Resources -> Versions/Current/Resources
Versions/
A/
MyFramework
Resources/
English.lproj/
InfoPlist.strings
Info.plist
B/
MyFramework
Resources/
English.lproj/
InfoPlist.strings
Info.plist
Current -> B
通过Versions/Current
符号链接,MyFramework.framework/MyFramework
指向框架当前版本的动态共享库,Versions/B/MyFramework
。由于使用符号链接,在程序的链接过程中,链接器会找到框架库的最新版本。这种安排确保新程序链接到框架的最新主要版本,并且使用框架早期主要版本构建的程序继续保持不变。有关主要和次要框架版本以及一般版本控制的更多信息,请参阅框架版本。有关使用动态库的更多信息,请参阅 动态库编程主题。
**重要提示:**对于链接器查找和链接动态库,框架的名称(不带.framework
扩展名)、符号链接和动态库必须相同。
附加目录
框架通常包含比Resources
目录更多的目录。例如,Headers
、Documentation
和Libraries
。因此,将Headers
目录添加到清单2中的示例将产生清单3中所示的框架。
例 3 具有附加资源类型的框架
MyFramework.framework/
Headers -> Versions/Current/Headers
MyFramework -> Versions/Current/MyFramework
Resources -> Versions/Current/Resources
Versions/
A/
Headers/
MyHeader.h
MyFramework
Resources/
English.lproj/
Documentation
InfoPlist.strings
Info.plist
B/
Headers/
MyHeader.h
MyFramework
Resources/
English.lproj/
Documentation
InfoPlist.strings
Info.plist
Current -> B
要在框架中创建其他目录,您必须将构建阶段添加到 Xcode 中的相应目标。
复制文件构建阶段允许您创建目录并将选定的文件复制到这些目录中。
表 1列出了您可能添加到框架中的一些典型目录。
目录 | 描述 |
---|---|
Headers | 包含您想要向外部开发人员提供的任何公共标题。 |
Documentation | 包含描述框架接口的 HTML 或 PDF 文件。 通常,文档目录不位于框架的顶层。 相反,它们位于特定于语言的资源目录中。 |
Libraries | 包含框架所需的任何辅助动态库。 |
框架配置
框架需要的配置类型与任何其他类型的包相同。
在框架的信息属性列表中,您应该包含表 2中列出的键。
在 Xcode 中设置框架属性时,大多数键都会自动包含在内,但您必须手动添加一些键。
钥匙 | 描述 |
---|---|
CFBundleName | 框架显示名称 |
CFBundleIdentifier | 框架标识符(Java 风格的包名称) |
CFBundleVersion | 框架版本 |
CFBundleExecutable | 框架共享库 |
CFBundleSignature | 框架签名 |
CFBundlePackageType | 框架包类型(始终为'FMWK' ) |
NSHumanReadableCopyright | 框架的版权信息 |
CFBundleGetInfoString | Finder 的描述字符串 |
由于框架永远不会与文档直接关联,因此您永远不应指定任何文档类型。
如果您愿意,您可以包含显示名称信息。
有关配置和信息属性列表的更多常规信息,请参阅 运行时配置指南。
有关表 2中每个键的具体详细信息,请参阅 信息属性列表键参考。
2、伞形框架束结构
伞形框架的结构与标准框架的结构类似,应用程序在链接到伞形框架和标准框架时不会区分伞形框架和标准框架。
但是,伞形框架与其他框架有两个区别。
第一是它们包含头文件的方式。
第二是它们封装了子框架。
雨伞框架的目的
伞式框架的目的是为特定应用程序环境中的编程提供所有必要的接口。伞式框架隐藏了许多不同系统软件之间复杂的交叉依赖关系。因此,您不需要知道必须导入哪些框架和库来完成特定任务。伞式框架还通过使用预编译的头文件使更快的构建成为可能。
伞式框架简单地包括组成子框架和其他公共框架并与之链接。伞式框架包含定义应用程序环境或系统软件层的所有技术和API。它还在外部开发人员将他们的程序与苹果工程提供的实现之间提供了一层抽象。
一个子框架在结构上是一个公共框架,它打包了特定的Apple技术,例如Apple Event、Quartz或Open Transfer。但是,子框架是公开的,但有限制。尽管子框架的API是公开的,但Apple已经建立了防止开发人员直接与子框架链接的机制(参见子框架链接的限制)。子框架总是驻留在安装在/System/Library/Frameworks
中的伞式框架中,在这个伞式框架中,它的头文件是公开的。
一些伞式框架包括其他伞式框架;Carbon和Cocoa应用程序环境的伞式框架尤其如此。例如,Carbon和Cocoa都(直接或间接)导入并链接到核心服务伞式框架(CoreServices.framework
)。反过来,这个伞式框架导入并链接到核心基金会等子框架。
伞式框架中子框架的确切组成是内部实现细节,可能会发生变化。通过提供一定程度的间接性,伞式框架将开发人员与这些变化隔离开来。Apple可能会在伞式框架内重组子框架,并可能添加、重命名或删除子框架内的头文件。如果您包含子框架的主头文件,这些更改不应该影响您的程序。
Umbrella框架包
从物理上讲,伞形框架与标准框架具有相似的结构。一个显著的区别是添加了一个Frameworks
目录来包含构成伞形框架的子框架。
清单4显示了核心服务框架的部分列表。(子框架的内容不包括在内,因为它们无论如何都没有被引用。)与标准框架一样,顶层项目是指向框架目录结构中更深层项目的符号链接。在这种情况下,链接的库和目录位于框架的文件夹A
中。
例 4 核心服务框架结构
CoreServices.framework/
CoreServices -> Versions/Current/CoreServices
CoreServices_debug -> Versions/Current/CoreServices_debug
CoreServices_profile -> Versions/Current/CoreServices_profile
Frameworks -> Versions/Current/Frameworks
Headers -> Versions/Current/Headers
Resources -> Versions/Current/Resources
Versions/
A/
CoreServices
CoreServices_debug
CoreServices_profile
Frameworks/
CarbonCore.framework
CFNetwork.framework
OSServices.framework
SearchKit.framework
WebServicesCore.framework
Headers/
Components.k.h
CoreServices-gcc3.p
CoreServices-gcc3.pp
CoreServices.h
CoreServices.p
CoreServices.pp
CoreServices.r
Resources/
Info-macos.plist
version.plist
Current -> A
与标准框架不同,伞形框架的Headers
目录包含一组更有限的头文件。
它不包含其子框架中的头文件集合。
相反,它仅包含框架的主头文件。
在源文件中引用伞形框架时,应仅包含主头文件。
有关更多信息,请参阅Including Frameworks。
四、框架版本
您可以根据对动态共享库所做的更改类型创建框架的不同版本。
版本有两种类型:主要版本(或不兼容版本)和次要版本(或兼容版本)。
两者都会对链接到框架的程序产生影响,尽管影响方式不同。
1、主要版本
框架的主要版本也称为不兼容版本,因为它破坏了与链接到框架动态共享库的先前版本的程序的兼容性。
任何在较新版本的框架下运行的此类程序都可能因为对框架所做的更改而遇到运行时错误。
以下部分描述了如何在框架中指定主要版本信息以及系统如何使用该信息来确保应用程序可以运行。
1.1 主要版本编号方案
由于框架的所有主要版本都保留在框架包中,因此与当前版本不兼容的程序在需要时仍可以在旧版本上运行。
每个主要版本的路径都对版本进行了编码(请参阅框架包结构)。
例如,以下路径中的字母“A”表示假设框架的主要版本:
/System/Library/Frameworks/Boffo.framework/Versions/A/Boffo
构建程序时,链接器会将此路径记录在程序可执行文件中。
动态链接编辑器在运行时使用该路径来查找框架库的兼容版本。
因此,主版本控制方案通过包含所有主版本并记录每个可执行文件要运行的主版本来实现框架的向后兼容性。
1.2 何时使用主要版本
当您对框架或动态共享库进行更改,而这些更改可能会破坏与其链接的程序时,您应为其创建新的主要版本。
以下更改可能会导致程序中断:
- 删除公共接口,例如类、函数、方法或结构
- 重命名任何公共接口
- 更改结构的数据布局
- 添加、更改或重新排序类的实例变量
- 向 C++ 类添加虚方法
- 重新排序 C++ 类的虚方法
- 更改 C++ 编译器或编译器版本
- 更改公共函数或方法的签名
函数签名的更改包括更改参数的顺序、类型或数量。
签名也可以通过从函数或其参数中添加或删除const
标签来更改。
当您更改框架的主要版本时,通常会将新版本设为“当前”版本。
Xcode 会自动生成一个符号链接网络,以指向框架的当前主要版本。
有关详细信息,请参阅框架包结构。
1.3 避免主要版本变更
您应该尽可能避免创建框架的主要版本。
框架的每个新主要版本都需要比相应的次要版本更改更多的存储空间。
在许多情况下,添加新的主要版本是不必要的。
在您需要创建框架的新主要版本之前,请仔细考虑框架的实现。
以下列表显示了在不需要新主要版本的情况下合并功能的方法:
- 用保留字段填充类和结构。
每当向公共类添加实例变量时,都必须更改主版本号,因为子类取决于超类的大小。
但是,您可以通过定义未使用的(“保留”)实例变量和字段来填充类和结构。
然后,如果您需要向类添加实例变量,您可以定义一个包含所需存储空间的全新类,并让保留的实例变量指向它。
请记住,填充频繁实例化的类的实例变量或频繁分配的结构的字段会消耗内存。 - 不要发布类、函数或方法名称,除非您希望客户使用它们。
您可以自由更改私有接口,因为您可以确保没有程序正在使用它们。
在私有标头中声明任何可能更改的接口。 - 不要删除接口。
如果某个方法或函数不再有任何有用的工作可做,请出于兼容性目的保留它。
确保它返回一些合理的值。
即使您向方法或函数添加了其他参数,也请尽可能保留旧形式。 - 请记住,如果您添加接口而不是更改或删除它们,则不必更改主版本号,因为旧接口仍然存在。
此规则的例外是实例变量。
虽然上述许多更改不需要创建框架的主要版本,但大多数更改需要更改次要版本信息。
有关更多信息,请参阅 Versioning Guidelines。
1.4 创建框架的主要版本
当您创建框架的主要版本时,Xcode 会为您处理大部分实现细节。
您只需指定主要版本指示符即可。
此指示符的流行惯例是字母表中的字母,每个新版本指示符都比前一个版本“增加”。
但是,您可以使用适合您需要的任何惯例,例如“2.0”或“Two”。
要在 Xcode 中设置框架的主要版本信息,请执行以下操作:
- 在 Xcode 2.4 中打开您的项目。
- 在“组和文件”窗格中,选择框架的目标并打开检查器窗口。
- 选择检查器窗口的“Build”选项卡。
- 在打包设置中,将“框架版本”设置的值设置为框架新主版本的标识符,例如
B
…
您还可以创建独立动态共享库(即未包含在框架包中的库)的主要版本。
独立库的主要版本编码在文件名本身中,如以下示例所示:
libMyLib.B.dylib
为了更轻松地更改主要版本,您可以创建一个符号链接,其名称libMyLib.dylib
指向库的新主要版本。
使用该库的程序可以引用该符号链接。
当您需要更改主要版本时,您可以更新链接以指向新库。
2、次要版本
在框架的主要版本中,您还可以指定当前的次要版本。
次要版本也称为“兼容版本”,因为它们保留了与链接到框架的应用程序的兼容性。
次要版本不会更改框架的现有公共接口。
相反,它们会添加新接口或修改框架的实现,从而提供新行为而不更改旧行为。
在框架的任何主要版本中,每次只存在一个次要版本。
后续次要版本会直接覆盖前一个版本。
这与主要版本方案不同,在主要版本方案中,框架包中共存有多个主要版本。
以下部分描述了如何在框架中指定次要版本信息以及系统如何使用该信息来确保应用程序可以运行。
2.1 次要版本编号方案
框架使用两个单独的数字来跟踪次要版本信息。
当前版本号跟踪框架的各个版本,主要供您的团队内部使用。
您可以使用此数字来跟踪框架的一组更改,并且可以根据需要随时增加它。
一个典型的例子是,每次您构建框架并将其分发给内部开发和测试团队时,都会增加此数字。
框架的兼容版本号更为重要,因为它标记了框架公共接口的变更。
当您对公共接口进行某些类型的变更时,应将兼容版本设置为与框架的当前版本匹配。
因此,兼容版本通常落后于当前版本。
只有当您的框架的公共接口发生变更时,这两个数字才会匹配。
请记住,并非所有对框架公共接口的更改都可以通过增加兼容版本来匹配。
某些更改可能需要您发布框架的新主要版本。
请参阅版本控制指南,了解您可以针对每个版本进行的更改的摘要。
2.2 何时使用次要版本
当你进行以下任何更改时,你应该更新框架的版本信息:
- 添加课程
- 向 Objective-C 类添加方法
- 向 C++ 类添加非虚拟方法
- 添加公共建筑
- 添加公共函数
- 修复不会改变公共接口的错误
- 进行不改变公共接口的增强
每次更改框架的公共接口时,您都必须更新其兼容版本号。
如果您的更改仅限于不影响框架公共接口的错误修复或增强功能,则无需更新兼容版本号。
2.3 运行时兼容版本号
当程序在开发过程中与框架链接时,链接器会在程序的可执行文件中记录开发框架的兼容版本。
在运行时,动态链接编辑器会将该版本与用户系统上安装的框架的兼容版本进行比较。
如果程序文件中记录的值大于用户框架中的值,则用户框架太旧,无法使用。
框架太旧的情况并不常见,但并非不可能。
例如,当用户运行的 OS X 版本比软件所需的版本要旧时,就会发生这种情况。
当发生这种不兼容情况时,动态链接编辑器会停止启动应用程序并向控制台报告错误。
要在早期版本的框架上运行应用程序,您必须将应用程序链接到它支持的框架的最早版本。
Xcode 提供对早期版本的 OS X 框架的链接支持。
此功能允许您链接到 OS X 10.1 及更高版本的特定版本。
有关更多信息,请参阅 SDK 兼容性指南。
2.4 创建框架的次要版本
如果您正在开发框架,则需要在适当的时间增加当前版本号和兼容版本号。
您可以在 Xcode 中设置这两个数字。
Xcode 框架项目的链接选项包括用于指定框架的当前版本和兼容版本的选项。
要为特定目标设置这些数字,请执行以下操作:
- 在 Xcode 2.4 中打开您的项目。
- 在“组和文件”窗格中,选择框架的目标并打开检查器窗口。
- 选择检查器窗口的“Build”选项卡。
- 在链接设置中,将“当前库版本”设置的值设置为框架的当前版本。
- 根据需要更新“兼容版本”选项,以反映框架的重大变化。
(有关更多信息,请参阅运行时的兼容版本号。)
注意: 在 Xcode 中,当前版本号和兼容版本号通常应该与目标相关联,而不是项目。
3、版本控制指南
每当更改框架中的代码时,您还应修改框架项目中的配置信息以反映更改。
对于简单的更改(例如错误修复和大多数新界面),您可能只需增加与框架相关的次要版本号。
对于更广泛的更改,您必须创建框架的新主要版本。
表 1列出了您可以对框架代码进行的更改类型以及您必须对项目文件进行的相应配置更改。
大多数需要主要或次要版本更新的更改都是因为这些更改会影响公共接口。
版本类型 | 允许更改 | 该怎么办 |
---|---|---|
主要版本 | 删除接口(类、函数、方法等)。 重命名接口。 更改类或结构的布局。 添加、更改或重新排序类的实例变量。 添加或重新排序 C++ 类中的虚拟方法。 更改函数或方法的签名。 更改 C++ 编译器或编译器版本。 | 您必须更改项目的主要版本指示符。 |
构建您的框架并将新版本合并到您现有的框架目录结构中。 | ||
次要版本(公共界面变更) | 添加 C++ 或 Objective-C 类。 向 Objective-C 类添加方法。 向 C++ 类添加非虚拟方法。 添加结构。 添加函数。 | 增加当前版本号并设置兼容版本号以匹配。 构建您的框架。 |
次要版本(无公共界面变化) | 修复不影响编程接口的错误。 进行不影响编程接口的增强。 更改仅在框架内部使用的接口。 | 增加当前版本号。 不要更改兼容版本号。 |
如果您没有在需要时更改框架的主版本号,则与其链接的程序可能会以不可预测的方式失败。
相反,如果您在不需要时更改主版本号,则系统会因不必要的框架版本而变得混乱。
由于主要版本可能会对开发造成很大干扰,因此最好尽可能避免使用它们。
避免主要版本更改描述了可用于进行重大更改而无需发布框架新主要版本的技术。
五、框架和绑定
Mach-O 库的动态绑定为 OS X 带来了强大的功能和灵活性。
通过动态绑定,框架可以透明地更新,而无需应用程序重新链接到它们。
在运行时,库代码的单个副本在使用它的所有进程之间共享,从而减少内存使用并提高系统性能。
注意: 有关绑定和动态链接的更详细说明,请参阅OS X ABI Mach-O 文件格式参考。
1、动态共享库
框架包中的可执行代码是动态链接的共享库,或者简称为动态共享库。
此库的代码可由多个同时运行的程序共享。
动态共享库带来诸多好处。
其中一个好处是它们可以更高效地利用内存。
所有程序共享同一份代码副本,而不是在内存中保留一份代码副本。
动态共享库还使开发人员更容易修复库代码中的错误。
由于库是动态链接的,因此无需重建依赖它的程序即可安装新库。
符号绑定
动态共享库具有与静态链接共享库不同的特点。
对于静态链接共享库,在链接时会检查库中的符号以确保它们存在。
如果它们不存在,则会发生链接错误。
对于动态共享库,未定义符号的绑定会延迟到程序执行时。
更重要的是,动态链接编辑器仅在程序引用符号时解析每个未定义符号。
如果符号未被引用,则不会将其绑定到程序。
在运行时绑定符号的能力是由 Mach-O 动态共享库的内部结构实现的。
组成库的目标代码模块在构建时保留了各自的边界;也就是说,源模块的代码不会合并到单个模块中。
在运行时,动态链接编辑器仅在需要时自动加载和链接模块。
换句话说,只有当程序引用该模块中的符号时才会链接模块。
如果未引用特定模块中的符号,则不会链接该模块。
[图 1] 说明了这种“延迟链接”行为。
在此示例中,当调用库函数a
时,模块a.o
在程序的main
例程中被链接。
当调用程序函数doThat
中的库函数b
时,模块b.o
被链接。
模块c.o
永远不会被链接,因为它的函数永远不会被调用。
图 1 动态共享库模块的惰性链接
组织你的框架代码
作为框架开发人员,在设计动态共享库时,您应该考虑到这种按需链接单独模块的方式。
由于动态链接编辑器始终尝试在继续处理其他模块和其他库之前绑定同一模块内的未解析符号,因此您应该确保将相互依赖的代码放在其自己的模块中。
例如,自定义分配和释放例程应放在同一个模块中。
此技术可防止使用错误的符号定义。
当符号的定义存在于多个动态共享库中并且其他符号定义覆盖正确的定义时,可能会出现此问题。
创建框架时,必须确保每个符号在库中只定义一次。
此外,库中不允许使用“common”符号;您必须使用单个真实定义,并在C代码中的所有其他定义之前加上extern
关键字。
当您构建程序并将其链接到动态共享库时,库的安装路径会记录在程序中。
对于 Apple 提供的系统框架,该路径是绝对路径。
对于第三方框架,该路径是相对于包含框架的应用程序包的路径。
捕获库路径可提高程序的启动性能。
动态链接编辑器无需搜索文件系统,而是直接转到动态共享库并将其链接到程序中。
这显然意味着,要使程序运行,必须将任何所需的库安装在记录路径指示可以找到的位置,或者必须将其安装在框架和库的标准后备位置之一。
有关更多信息,请参阅安装框架。
库依赖项
动态共享库的客户端无需了解库所需的任何依赖项。
构建动态共享库时,静态链接器会将有关任何依赖库的信息存储在动态共享库可执行文件中。
在运行时,动态链接编辑器会读取此信息并根据需要使用它来加载依赖库。
为每个依赖库存储的另一个重要信息是所需版本。
框架和动态共享库具有与之关联的版本信息。
在运行时,存储的版本信息会与可用库的实际版本进行比较。
如果可用库太旧,动态链接编辑器可能会终止程序以防止出现不良行为。
有关库版本控制的更多信息,请参阅框架版本。
独立动态共享库
除了创建框架之外,您还可以创建独立的动态共享库。
按照惯例,独立的动态共享库具有.dylib
扩展名和通常安装在 /usr/lib
中。
库文件包含库所需的所有代码和资源。
对于大多数开发人员来说,创建独立的动态共享库是一种不常见的方法。
在大多数情况下,框架是一种首选方法。
框架的捆绑结构使其能够包含复杂的资源类型,例如 nib 文件、图像和本地化字符串。
2、框架和预绑定
在 OS X v10.3.4 之前,OS X 使用一项名为预绑定的功能来消除可执行文件链接到动态库时产生的加载时间延迟。
预绑定涉及预先计算系统上每个框架和库中的符号地址。
此预先计算的目的是避免库和框架之间的地址空间冲突。
此类冲突会在加载时产生巨大的性能损失,并会明显减慢应用程序的启动时间。
OS X v10.3.4 中对动态加载器的改进使得预绑定基本不再必要。
动态加载器本身经过修改,可以更有效地处理加载时冲突。
使用新的动态加载器,未预绑定的应用程序的启动速度通常至少与在早期版本的系统中预绑定时一样快(有时甚至更快)。
在 OS X v10.4 中,预绑定行为进行了另一项更改,以减少安装新软件后“优化”系统所花费的时间。
现在,仅预绑定选定的系统框架,而不是预绑定所有框架和库。
通过有选择地选择要预绑定的框架,预绑定工具能够将系统最常用的框架紧密打包到比以前更小的内存空间中。
此步骤减少了为 Apple 框架保留的空间量,并将其返还给第三方应用程序和框架。
如果您开发的框架要在 OS X 10.4 之前的版本上运行,您仍应启用预绑定并指定首选地址。
如果您开发的框架适用于 OS X v10.4 或更高版本,则无需预绑定。
在更高版本的系统上预绑定框架不会降低性能,但需要一些额外的配置步骤,这些步骤将在后面的部分中描述。
注意: 只有 OS X 版本中的 Mach-O 可执行文件支持预绑定。
预绑定您的框架
如果您正在开发在OS X v10.4或更早版本上运行的框架,则应在Xcode项目中指定框架的首选绑定地址。
以下步骤向您展示了如何为Xcode框架项目配置预绑定:
- 在Xcode中打开您的项目。
- 在组和文件窗格中,选择您的目标,打开其信息窗口,然后单击构建。
- 确保预绑定构建设置已打开(您可以在搜索字段中输入
prebinding
以找到它)。 - 在其他链接器标志构建设置中,添加
-seg1addr
标志以及框架的首选地址。例如,要将框架的首选地址设置为0xb0000000
,您需要输入:
-seg1addr 0xb0000000
- 像往常一样构建和链接您的框架。
在预绑定框架时,使用-seg1addr
选项指定首选地址尤其重要。如果启用预绑定但未指定首选地址,Xcode将使用默认地址0x00000000
。
这是一个问题,因为该地址也是所有应用程序的首选地址。
相反,您应该将初始地址设置为为应用程序代码和框架保留使用的内存区域。有关有效地址范围的列表,请参阅*启动时间性能指南*中的“预绑定您的应用程序”。
您可以通过使用otool
命令检查二进制文件来确认框架的首选地址。
有关详细信息,请参阅查找框架的首选地址。
预装订注意事项
如果您要预绑定您的框架以便它可以在10.4之前的OS X版本上运行,您应该注意以下注意事项:
- 您的框架占用的地址范围不应与您正在开发的任何其他库或框架的地址范围重叠。
如果您的框架可能必须与其他第三方库或框架共存,您可以使用otool
查找那些第三方产品的首选地址。 - 您的框架不得包含对任何未定义符号的引用。
- 您的框架不能覆盖在平面命名空间库中定义的符号。
例如,您不能定义自己的malloc
例程然后使用平面命名空间库进行预绑定。 - 两个框架(或库)不能具有循环依赖关系。
- 您的框架应始终使用两级命名空间,以避免与其他框架和库中的符号发生名称冲突。
- 请记住,要预绑定应用程序,其所有依赖框架也必须预绑定构建。
为您的框架选择一个唯一的首选地址可能很棘手,尤其是当它必须与许多第三方框架共存时。Apple提供了一组有效的范围供您的框架和应用程序使用。(请参阅*启动时间性能指南* 中的“预绑定您的应用程序”。)
但是,您可能仍然会遇到与公司内部或外部其他团队开发的框架重叠的领域。
即使框架之间确实发生重叠,您的预绑定努力也不会白费。动态链接器在运行时立即纠正重叠,根据需要移动框架。在10.4之前的OS X版本中,守护进程还会在后台运行,为信息过期的应用程序重新计算预绑定信息。
查找框架的首选地址
要查找框架的首选地址,请使用带有-l
选项的otool
命令来显示框架二进制文件的加载命令。
加载命令包括加载二进制文件每个段的虚拟内存地址。
因为大多数段位于库开头的偏移处,所以您需要查看初始LC_SEGMENT
命令来查找库的首选基址。
例如,假设您创建了一个库,并在Xcode项目中为其分配了首选地址0xb0000000
。
从终端窗口在库上运行otool -l
将显示类似于以下内容的初始加载命令:
Load command 0
cmd LC_SEGMENT
cmdsize 328
segname __TEXT
vmaddr 0xb0000000
vmsize 0x00002000
fileoff 0
filesize 8192
maxprot 0x00000007
initprot 0x00000005
nsects 4
flags 0x0
注意vmaddr
字段的值。
该字段表示二进制文件的首选地址与您在 Xcode 项目中指定的地址相匹配。
Apple 框架和预绑定
在 OS X 10.4 之前的版本中,Apple 提供的框架是预绑定的,并被分配到内存的保留区域。
在 OS X v10.4 及更高版本中,Apple 系统框架会在您安装操作系统时动态预绑定。
在这两种情况下,Apple 保留的内存范围均列在*启动时间性能指南*中的“预绑定您的应用程序”中。
六、框架和弱链接
开发人员面临的一个挑战是利用 OS X 新版本中引入的新功能,同时仍支持旧版本的系统。
通常,如果应用程序使用框架中的新功能,则无法在不支持该功能的早期版本的框架上运行。
当尝试使用该功能时,此类应用程序要么无法启动,要么崩溃。
Apple 通过增加对弱链接符号的支持解决了这个问题。
当框架中的符号被定义为弱链接时,该符号在运行时不必存在,进程就可以继续运行。
静态链接器会在引用该符号的任何代码模块中识别弱链接符号。
动态链接器在运行时使用相同的信息来确定进程是否可以继续运行。
如果框架中不存在弱链接符号,则只要代码模块不引用该符号,它就可以继续运行。
但是,如果符号存在,代码可以正常使用它。
如果您要更新自己的框架,则应考虑将新符号设为弱链接。
这样做可以让您的框架的客户端更轻松地支持它。
您还应确保自己的代码在使用弱链接符号之前检查它们是否存在。
注意: 尽管代码片段管理器支持其自己的弱链接形式,但以下信息仅适用于 Mach-O 可执行文件。
有关弱链接的更多信息(包括其他示例),请参阅 SDK 兼容性指南。
1、弱链接和 Apple 框架
Apple 框架使用可用性宏来确定符号是弱链接还是强链接。
Apple 使用可用性宏在其框架中包装新接口,以指示某个功能首次出现在哪个版本的操作系统中。
宏还用于指示已弃用的功能和接口。
/usr/include/AvailabilityMacros.h
中定义的可用性宏 会根据项目支持的 OS X 版本向系统接口添加弱链接信息。
创建新项目时,通过在 Xcode 中设置部署目标和目标 SDK,告诉编译器项目支持哪些版本的 OS X。
编译器使用这些设置分别为MAC_OS_X_VERSION_MIN_REQUIRED
和 MAC_OS_X_VERSION_MAX_ALLOWED
宏分配适当的值。
有关如何在 Xcode 中修改这些设置的信息,请参阅 *SDK 兼容性指南*或 Xcode 帮助中的“在 Xcode 中设置交叉开发” 。
例如,假设您在 Xcode 中将部署目标(最低要求版本)设置为 OS X v10.2,将目标 SDK(最高允许版本)设置为 OS X v10.3。
在编译期间,编译器将弱链接 OS X 版本 10.3 中引入的任何接口,同时强链接早期接口。
这将允许您的应用程序继续在 OS X 版本 10.2 上运行,但仍可利用新功能(当它们可用时)。
重要提示: Xcode 中的部署目标设置必须设置为 OS X 10.2 或更高版本才能利用弱链接。
如果您不设置此值(或将其设置为 OS X 的早期版本),则无法在项目中使用弱链接。
在使用高于最低要求版本的 OS X 版本中引入的任何符号之前,请务必先检查该符号是否存在。
有关更多信息,请参阅 使用弱链接符号。
2、弱链接标记符号
如果您定义自己的框架,则可以使用weak_import
属性将符号标记为弱链接。
如果您向现有框架引入新功能,则弱链接尤其合适。
要将符号标记为弱链接,您必须确保您的环境配置为支持弱链接:
- 您必须使用 GCC 3.1 或更高版本。
GCC 2.95 版本不支持弱链接 - 您必须将 Xcode 项目的 OS X Deployment Target 构建选项设置为 OS X v10.2 或更高版本。
除非您明确指定其他方式,否则链接器会将符号标记为强链接。
要将函数或变量标记为弱链接,请将weak_import
属性添加到函数原型或变量声明中,如以下示例所示:
extern int MyFunction() __attribute__((weak_import));
extern int MyVariable __attribute__((weak_import));
3、使用弱链接符号
如果您的框架依赖于任何 Apple 或第三方框架中的弱链接符号,则必须在使用之前检查这些符号是否存在。
如果您在未先检查的情况下尝试使用不存在的符号,动态链接器可能会生成运行时绑定错误并终止相应的进程。
如果框架中没有弱链接符号,则链接器会将该符号的地址设置为 NULL。
您可以使用类似以下代码在代码中检查此地址:
extern int MyWeakLinkedFunction() __attribute__((weak_import));
int main()
{
int result = 0;
if (MyWeakLinkedFunction != NULL)
{
result = MyWeakLinkedFunction();
}
return result;
}
注意: 检查符号是否存在时,必须将其与代码中的NULL
或nil
显式进行比较。
不能使用否定运算符 (!
) 来否定符号的地址。
4、与整个框架的弱链接
当您引用另一个框架中的符号时,大多数符号都会与您的代码紧密链接。
为了创建指向符号的弱链接,包含该符号的框架必须明确weak_import
为其添加属性(请参阅将符号标记为弱链接)。
但是,如果您不维护框架并且需要将其符号弱链接,则可以明确告诉编译器将所有符号标记为弱链接。
为此,您必须在 Xcode 中打开您的项目并修改目标链接到框架的方式,如下所示:
- 选择您想要修改的目标并显示其构建阶段。
- 展开“链接二进制文件与库”构建阶段以查看目标当前链接的框架。
- 如果您想要弱链接到的框架在“链接二进制与库”构建阶段中列出,请选择它,然后选择“编辑”>“删除”将其删除。
现在您可以告诉链接器对该框架使用弱链接。 - 选择目标,打开其信息窗口,然后单击“构建”。
- 对于其他链接器标志构建设置,添加以下命令行选项规范,其中
<framework_name>
是要弱链接到的框架的名称:
-weak_framework <框架名称>
- 打造您的产品。
-weak_framework
选项告知链接器对指定框架中的所有符号进行弱链接。
如果您需要链接到库而不是框架,则可以使用 -weak_library
链接器命令而不是-weak_framework
。
七、创建框架的指南
以下部分提供了有关如何最好地实现自定义框架的指导。
1、API 命名指南
全局命名空间中的元素(例如类、函数和全局变量)都应具有唯一的名称。
虽然两级命名空间可帮助动态链接编辑器在运行时找到正确的符号,但该功能并不能防止由于符号定义过多而导致的静态链接错误。
例如,假设两个不同的框架定义了一个同名的符号。
如果您要创建一个包含这两个框架的项目,则在引用有问题的符号时会遇到静态链接错误。
静态链接器负责选择要使用的框架,然后生成动态链接编辑器所需的两级命名空间提示。
由于两个框架都定义了该符号,因此静态链接器无法选择并会生成错误。
您应该尝试选择能够明确将每个符号与您的框架关联起来的名称。
例如,考虑为所有外部符号名称添加一个简短的前缀。
前缀有助于区分您框架中的符号与其他框架和库中的符号。
它们还可以让其他开发人员清楚地了解正在使用哪个框架。
典型的前缀包括框架名称的前几个字母或首字母缩写。
例如,Core Graphics 框架中的函数使用前缀“CG”。
如果您正在为 Objective-C 类编写类别,您还应该在方法名称中使用某种独特的前缀。
由于类别可以从多个来源动态加载,因此独特的前缀有助于确保您的类别中的方法不会与其他类别中的方法冲突。
有关 Cocoa 命名约定的详细指南,请参阅 Cocoa 编码指南。
2、框架的性能影响
在实际创建自定义框架之前,您应该仔细考虑其预期用途。
在运行时加载和使用框架会产生一定量的开销。
虽然这种开销并不高(特别是如果您的框架和应用程序是预绑定的),但在某些情况下可能没有必要。
当多个应用程序共享其代码时,框架是最有效的。
如果您正在设计一套应用程序,您可以创建一个自定义框架来存储套件中所有应用程序访问的通用代码。
同样,您可能希望在内部创建一个私有框架,以将通用的可重用代码与应用程序特定的代码分开。
虽然您可以将此框架嵌入到您创建的每个应用程序中,但这样可以简化可重用代码的维护。
3、你的框架中应该包含哪些内容
框架为多个应用程序提供共享资源。
如果您要定义自定义框架,则应牢记这一目标。
您的框架应尽可能不包含与特定应用程序绑定的代码。
相反,它应包含可重复使用的代码或多个应用程序共用的代码。
除了代码之外,您还可以在框架中包含其他类型的资源。
在框架的Resources
目录中,您可以包含 nib 文件、图像、声音文件、本地化文本以及您可能在应用程序中找到的任何其他类型的资源。
在运行时,应用程序使用与它们用于特定于应用程序的资源相同的捆绑机制来加载框架的资源。
4、在框架代码中使用 C++
从动态共享库导出 C++ 类接口存在一些固有问题。
这些问题是由于缺乏区分 C++ 语言和 C 的扩展的标准化调用约定造成的。
为了消除使用不同编译器构建的库之间的链接问题,编译器供应商同意支持一套标准化的 C 语言调用约定。
这些标准要求供应商以一致的方式生成 C 代码,以保证一个库中的代码可以调用另一个库中的代码。
虽然已经为 C++ 语言扩展提出了这样的标准,但尚未最终确定。
因此,无法保证两个不同的 C++ 库之间的调用能够正常工作。
如果您打算在框架中使用 C++ 代码,那么避免不兼容问题的最佳方法是使用 ANSI C 函数包装 C++ 类的接口。
通过使用 C++ 进行实现并使用 ANSI C 进行接口,您可以继续使用首选语言进行开发,同时为客户端提供兼容的接口以供调用。
如果您需要从框架中导出类接口,Apple 建议您使用 Objective-C 而不是 C++ 来定义类。
Objective-C 语言确实有一组标准化的调用约定,可让您从框架中公开类接口。
有关在共享库中使用 C++ 的更多信息和指导,请参阅 C++ 运行时环境编程指南。
5、不要创建伞状框架
虽然可以使用 Xcode 创建伞形框架,但对于大多数开发人员来说,这样做是不必要的,也不建议这样做。
Apple 使用伞形框架来掩盖操作系统中库之间的一些相互依赖关系。
在几乎所有情况下,您都应该能够将代码包含在单个标准框架包中。
或者,如果您的代码足够模块化,您可以创建多个框架,但在这种情况下,模块之间的依赖关系将很小或不存在,因此没有必要为它们创建伞形框架。
6、在哪里安装框架
OS X 在系统上的几个固定位置查找公共框架。
如果您要创建供其他开发人员使用的框架,则应将其放在这些位置之一。
如果您要创建私有框架,则可以将其嵌入应用程序或放在自定义位置。
无论哪种情况,您都必须做一些额外的工作来加载框架代码和资源。
有关在何处安装框架的详细信息,请参阅安装框架。
八、创建框架
一旦您决定需要为代码创建框架,您就可以使用 Xcode 轻松完成此操作。
在向框架添加主要版本时,您还需要能够维护您的项目。
以下部分将向您展示如何执行这两项任务。
1、创建你的框架
在 Xcode 中,选择“文件”>“新建项目”来创建项目。
按照提示选择所需的框架类型以及项目目录的放置位置。
Xcode 附带的默认模板允许您指定是否要创建 Carbon 或 Cocoa 框架。
您选择的框架类型决定了为您生成的默认文件。
如果您不想在框架中包含 Carbon 或 Cocoa 标头,您可以在创建项目后删除对它们的任何引用。
配置你的框架项目
当您创建新框架时,您可能需要修改几个配置选项。
这些选项使您可以更轻松地将框架分发给客户,并保证其在未来开发周期后的兼容性。
表 1列出了您应该为框架设置的一些选项。
选项 | 描述 |
---|---|
框架标识符 | Java 样式的包标识符,用于向系统唯一标识框架。 您应该始终设置此选项。 |
要在 Xcode 2.4 中设置此选项,请打开框架目标的检查器窗口,选择“属性”选项卡,然后修改“标识符”字段。 | |
框架版本 | 框架的当前主要修订版本。 有关更多信息,请参阅主要版本。 |
在 Xcode 2.4 中,使用框架版本构建设置为框架目标设置此值。 | |
当前版本 | 框架的当前修订版本。 在 Xcode 2.4 中,使用当前库版本构建设置为框架目标设置此值。 有关更多信息,请参阅次要版本。 |
兼容版本 | 框架的最新修订版本,包括对公共接口的更改。 在 Xcode 2.4 中,使用兼容版本构建设置为框架目标设置此值。 有关更多信息,请参阅次要版本。 |
导出符号 | 您要导出到其他程序的框架符号列表。 在 Xcode 2.4 中,使用导出符号文件构建设置为您的框架目标指定一个包含导出符号的文件。 要指定一个包含要隐藏的符号的文件,请使用未导出符号文件构建设置。 有关更多信息,请参阅导出您的框架接口。 |
安装路径 | 框架最终应安装到的目录名称。 在 Xcode 2.4 中,使用“安装目录”构建设置为框架目标设置此值。 请参阅“安装框架”以获取标准位置列表。 |
首选地址 | 对于在 OS X v10.3.9 及更早版本中部署的框架,请指定用于预绑定操作的首选内存地址。 在 10.4 及更高版本中部署框架时不需要此值。 有关如何设置框架首选地址的信息,请参阅框架和预绑定。 |
现场测试你的框架
当您构建框架时,Xcodebuild
默认将其放置在项目目录的子目录中。
虽然您可以告诉 Xcode 将您的框架安装在其最终部署位置,但在开发过程中您可能希望将其保留在原处。
如果这样做,您可能需要告诉测试应用程序在哪里找到您的框架。
如果您的框架项目包含测试应用的其他目标,则 Xcode 会在与您的框架相同的文件夹中构建这些应用。
与您的框架一起构建的测试应用会自动找到该框架,因为它们与框架距离较近。
但是,如果您将测试应用构建到不同的构建目录中,除非您告诉它们在哪里可以找到框架,否则这些应用可能无法找到您的框架。
应用程序查找框架的常用方法是在标准位置查找(请参阅安装框架)。
但是,您可能不想每次更改框架时都重新安装它。
在这种情况下,您可以使用DYLD_FRAMEWORK_PATH
环境变量告诉测试应用程序在哪里找到框架。
如果在标准位置找不到所需的框架,则将此变量添加到可执行文件告诉 dyld
在哪里查找其他框架。
以下步骤将向您展示如何在Xcode中设置此变量。
- 在 Xcode 中打开您的应用程序项目。
- 在“组和文件”窗格中,打开可执行文件组,选择要配置的可执行文件,打开其信息窗口,然后单击参数。
- 向环境变量列表添加一个条目。
- 将环境变量的名称设置为
DYLD_FRAMEWORK_PATH
。 - 将变量的值设置为包含框架的目录的完整路径名。
要指定多个框架目录,请用冒号分隔路径名。
例如,您可以在此行中使用如下值:/Users/lynn/MyFrameworks:/Volumes/Keylime/MyOtherFrameworks
。
2、在应用程序包中嵌入私有框架
如果您需要随应用程序分发私有框架,首选解决方案是将框架嵌入到应用程序包中。
嵌入框架将框架与应用程序紧密联系起来,并确保应用程序始终具有运行所需的正确版本的框架。
嵌入框架还可以向其他开发人员明确表示他们永远不应链接到该框架。
注意: 如果多个应用程序必须共享一个私有框架,则应将框架安装在系统上的可用PrivateFrameworks
目录中,而不是将其嵌入到一个(或所有)应用程序中。
共享一个框架可以更有效地重复使用框架的动态库。
有关将共享的私有框架放在何处的信息,请参阅私有框架的位置。
要在应用程序中嵌入框架,必须采取以下几个步骤:
- 您必须配置应用程序目标的构建阶段以将框架放在正确的位置。
- 您必须配置框架目标的安装目录,该目录告诉框架它将驻留在何处。
- 您必须配置应用程序目标,以便它引用其安装目录中的框架。
可以使用单个 Xcode 项目或多个项目构建和嵌入框架到应用程序中。
使用单个 Xcode 项目稍微容易一些,因为它需要较少的配置来构建框架和应用程序。
但是,对于多项目设置,一旦两个项目配置正确构建,嵌入框架的配置步骤与单个 Xcode 项目的配置步骤基本相同。
对两个目标使用一个 Xcode 项目
使用单个 Xcode 项目同时用于应用程序和框架目标可简化所需的设置。
创建项目后,只需向其中添加两个目标:一个用于应用程序,一个用于框架。
(由于两个目标位于同一个项目中,因此在构建时从任一目标查找源文件都不会出现问题。)之后,只需使用适当的运行时信息配置框架和应用程序目标即可嵌入。
框架目标的配置涉及告知其安装位置。
框架需要此信息,以便找到所需的资源。
由于框架通常安装在固定位置,因此您通常会指定相应框架目录的完整路径。
但是,当您将框架嵌入到包中时,框架的位置并不固定,因此您必须使用@executable_path
占位符让框架知道其位置相对于当前可执行文件。
- 打开框架目标的检查器并选择“构建”选项卡。
- 将安装目录构建设置的值设置为
@executable_path/../Frameworks
。
在构建时,Xcode 会构建框架并将结果放入构建目录中。
不过,在应用程序可以使用框架之前,您必须按如下方式配置应用程序目标:
- 您需要将框架复制到应用程序包中。
- 您需要将应用程序与框架链接起来。
- 您需要在框架和应用程序之间创建构建依赖关系。
以下步骤向您展示如何配置应用程序目标。
- 在“组和文件”窗格中,打开您的应用程序目标以查看其当前的构建阶段。
- 将您的框架产品(位于 Products 文件夹中)拖到应用程序目标的现有 Link Binary With Libraries 构建阶段。
这会导致应用程序链接到您的框架。 - 向应用程序目标添加新的复制文件构建阶段。
(此阶段将用于在应用程序包中安装框架。) - 选择新的构建阶段并打开检查器窗口。
- 在检查器窗口的“常规”选项卡中,将构建阶段的目标设置为“框架”。
- 将您的框架产品拖至新的构建阶段。
- 再次选择应用程序目标并打开检查器窗口。
- 在检查器窗口的“常规”选项卡中,将您的框架添加为应用程序的依赖项。
添加此依赖项会导致 Xcode 在构建应用程序目标之前构建框架目标。
您在应用程序目标中建立的构建依赖关系会导致先构建框架,然后再构建应用程序。
这很重要,因为它可以保证构建的框架版本可用于链接和嵌入应用程序。
由于这种依赖关系,您可以将 Xcode 项目的活动目标设置为应用程序并将其保留在那里。
现在构建应用程序会构建框架并将其复制到应用程序包目录,从而在两者之间创建必要的链接。
为每个目标使用单独的 Xcode 项目
如果您的框架和应用程序已经有单独的 Xcode 项目,您可以利用 Xcode 的跨项目引用将框架嵌入到您的应用程序中。
跨项目引用是创建两个单独的 Xcode 项目之间关系的便捷方式。
要在您的应用程序和框架之间设置跨项目引用,您需要执行以下操作:
- 在您的应用程序项目中,选择“项目”>“添加到项目”,然后选择框架的
.xcodeproj
文件。
Xcode 将添加框架项目,并在应用程序项目的“组和文件”窗格中显示其产品。 - 修改应用程序和框架目标的“构建产品路径”设置,使它们使用相同的构建目录。
您需要在原始 Xcode 项目文件中修改每个目标。 - 在您的应用程序项目中,通过添加包含任何框架头文件的目录来修改应用程序目标的 Header Search Paths 设置。
配置 Xcode 项目以正确构建后,您可以继续执行将框架嵌入应用程序所需的配置步骤。
框架和应用程序目标的其余配置步骤与使用单个 Xcode 项目构建两个目标中描述的步骤相同。
框架的安装目录必须配置为相对于应用程序的可执行路径。
同样,应用程序目标必须将框架复制到其包中并设置必要的链接和依赖项。
唯一的区别是您必须在自己的 Xcode 项目中配置每个目标。
3、构建框架的多个版本
在框架发布后,您应该考虑如何管理 Xcode 项目以备将来发布。
更新现有框架时,所做的更改类型决定了处理项目文件的最佳方式。
例如,重大更改可能需要复制项目文件并维护单独的项目(每个主要版本一个)。
另一方面,较小的更改可以合并到您现有的 Xcode 项目中。
更新次要版本
如果您对框架进行了微小更改,则无需为框架创建新的 Xcode 项目。
但是,您应该始终更新与框架相关的“当前版本”和“兼容版本”值。
这些值使动态链接器能够确定是否可以将程序链接到框架。
有关如何更新框架的次要版本信息以及构成次要版本更新的更改类型的更多信息,请参阅次要版本。
更新主要版本
更新框架主要版本的过程比更新次要版本的过程更困难。
创建新主要版本的推荐方法是复制整个 Xcode 项目文件夹并从那里继续开发。
旧项目文件应存档并用于执行旧版构建。
但是,新项目应继续积极开发。
有了新的项目文件夹后,您需要对 Xcode 项目进行一些修改,以将该项目标识为主要版本。
选择您的框架目标并打开 Inspector 窗口。
在 Build 窗格中,修改以下构建选项:
- 将框架版本构建设置的值增加到下一个连续值。
- 增加当前库版本构建设置的值。
- 更新兼容版本构建设置的值以匹配更新的当前库版本。
- 更新路径信息包括框架主要版本指示符的任何生成设置。
例如,如果您有一个基于产品目录的“复制文件”构建阶段,则可能需要更新该路径。
或者确保使用框架的Current
符号链接指定路径。 - 在框架目标信息窗口的属性窗格中,更新存储在框架信息属性列表中的版本号。
修改代码后,即可构建框架目标。
您将获得一个仅包含新主要版本的新框架包。
在安装过程中,如果目标系统上尚不存在框架的某个版本,请让安装程序按原样复制框架包。
但是,如果存在框架的现有版本,请让安装程序将新框架目录的内容复制到旧目录。
安装程序脚本必须将旧框架包中的符号链接替换为指向新版本框架的符号链接。
但是,复制新的主要版本应该保留所有旧版本。
这样,现有应用程序就可以继续运行,而较新版本则使用更新后的框架。
警告: 不要尝试在现有框架包上构建新版本的框架。
对项目进行全新构建会删除包的所有内容,包括所有旧版本。
维护单独的项目并在安装期间复制文件会更安全。
您可能还希望安装程序删除框架的过时版本的所有头文件或文档。
此步骤是可选的,由您自行决定。
但是,建议这样做,以防止开发人员在开发过程中意外包含一组过时的头文件或查看较旧的文档。
有关框架主要版本的更多信息,请参阅主要版本。
九、在运行时初始化框架
当某个框架首次由某个特定进程加载时,系统会调用该框架的初始化代码。
在 OS X v10.4 之前,框架初始化通常由一个在首次加载框架时调用的例程组成;然而,在 OS X v10.4 及更高版本中,使用模块初始化程序和终结器成为首选技术。
模块初始化程序的主要优势在于,它们可以在动态链接器有机会加载模块初始化程序所依赖的任何其他库之后被调用。
框架初始化例程则并非如此,它们在加载时立即被调用,并且可能在加载其他重要模块(如 C++ 运行时)之前发生。
1、初始化例程和性能
由于所有类型的初始化例程都是在框架加载时调用的,因此它们通常在应用程序启动时调用。
启动时通常不适合执行大量工作,因为这会使相应的应用程序感觉运行缓慢。
在编写初始化例程时,请尽量少做工作以使框架处于已知状态。
例如,不要立即分配框架数据结构,而应考虑在需要时延迟初始化数据结构。
此外,避免执行可能导致潜在延迟的任何操作,例如访问网络。
请记住,如果您的框架包含静态数据,则这些数据也必须在加载时初始化。
尽量减少框架使用的静态变量数量也有助于降低初始化性能。
有关缩短应用程序启动时间的更多信息,请参阅 启动时间性能指南。
2、定义模块初始化器和终结器
模块初始化程序是初始化框架的首选方法。
模块初始化程序是一个静态函数,不接受任何参数,也不返回任何值。
它使用编译器属性声明,如例 1constructor
所示。
与任何静态初始化函数一样,您应该通过在初始化代码周围放置一个保护变量来防止该函数被调用两次。
例 1 框架的模块初始化程序
__attribute__((constructor))
static void MyModuleInitializer()
{
static initialized = 0;
if (!initialized)
{
// Initialization code.
initialized = 1;
}
}
框架可以定义多个模块初始化函数。
动态链接编辑器在初始化任何静态变量之后但在调用框架中的任何其他函数或方法之前调用任何模块初始化函数。
如果模块初始化函数中的代码依赖于其他库(例如 C++ 运行时),则动态链接器会在调用该函数之前加载这些库。
每个模块初始化函数在进程加载框架时仅被调用一次。
模块初始化程序按照编译器遇到的顺序执行。
您的框架不得导出任何模块初始化函数的符号。
默认情况下,Xcode 会导出项目头文件中声明的所有符号。
您可以通过明确导出符号列表或隐藏特定符号(并导出其他所有内容)来限制导出的符号集。
有关如何配置框架导出的信息,请参阅导出框架接口。
在 OS X v10.4 及更高版本中,模块初始化程序可以访问当前进程的启动参数,如例 2所示。
框架可能会使用这些参数来获取有关应用程序启动配置的信息,例如启动行中传入的任何环境变量或标志。
例 2 带有启动参数的模块初始化程序
__attribute__((constructor))
static void initializer(int argc, char** argv, char** envp)
{
// Initialization code.
}
除了模块初始化函数之外,您还可以定义模块终结函数,这些函数为您的框架实现任何清理代码。
模块终结函数使用编译器属性声明,如例 3destructor
所示。
与模块初始化函数一样,模块终结函数的符号不得由您的框架导出。
模块终结函数的执行顺序与编译器遇到的顺序相反。
例 3 模块终结器函数
__attribute__((destructor))
static void finalizer()
{
// Clean up code.
}
3、使用初始化例程
如果您的框架必须在 10.4 之前的 OS X 版本中运行,您仍然可以根据需要使用框架初始化函数来初始化框架数据结构。
但是,在实现初始化例程时,务必要尽可能少地执行工作。
由于初始化例程在加载时立即运行,因此其他代码模块可能无法加载并可供使用,因此务必要不要执行涉及其他库的任何复杂初始化。
初始化函数的签名与标准模块初始化器的签名相同:
void MyFrameworkInit()
要加载例程,您必须使用标志将例程的名称传递给链接器INIT_ROUTINE
。
从命令行,您可以使用选项设置此标志-init
,后跟初始化例程的名称。
在 Xcode 中,您可以通过执行以下操作来设置此标志:
- 选择您的框架目标并打开检查器窗口。
- 在“构建”窗格中,找到“初始化例程”构建设置。
它与“链接”选项一起。 - 将此设置的值设置为初始化例程的符号名称。
对于基于 ANSI C 的例程,符号名称只是带有前导下划线字符的函数名称。
例如,MyFrameworkInit
函数的符号名称为_MyFrameworkInit
。
不应将 C++ 例程用于初始化函数。
如果您在使用指定的初始化例程构建框架时遇到问题,可以尝试以下几种方法来解决该问题。
使用nm
命令行工具确认例程的符号名称。
使用nm
时,您还可以-g
选项来确保库已导出例程的全局符号。
如果没有,请检查您是否有导出文件并确保您的例程包含在其中。
重要提示: 在 OS X v10.3.9 及更高版本中,尤其不要使用框架初始化函数中的 C++ 代码。
在 10.3.9 之前的版本中,C++ 运行时被打包为静态库并链接到应用程序可执行文件中。
在 OS X v10.3.9 及更高版本中,该库被打包为动态共享库并仅在需要时加载,这可能是在框架加载之后。
尝试使用 C++ 功能的框架初始化函数随后可能会失败或导致应用程序终止。
十、导出框架接口
当您使用 Xcode 构建框架或应用程序时,链接器默认会导出代码中定义的所有符号。
对于包含许多符号的运输框架,这可能会导致运行时的性能问题。
加载框架时,动态链接编辑器会加载与框架关联的符号。
如果框架包含许多私有函数,则不会使用这些私有函数的符号,但仍会与公共函数的符号一起加载。
加载这些额外的符号不仅会浪费内存,还需要在符号查找期间做更多工作来遍历列表。
在 Xcode 中,您可以通过在链接器选项中指定导出文件来限制可执行文件导出的符号。
1、创建你的导出文件
导出文件是一个简单的文本文件(.txt
或其他文本文件扩展名),其中包含要导出的符号列表。
要创建该文件,请将一个新的空文件添加到您的 Xcode 项目中。
在此文件中添加要导出的符号列表,每行一个符号。
对于基于 ANSI C 的代码,通常只需在函数或变量的名称前添加下划线字符即可获取符号名称。
对于使用混乱符号名称的语言(如 C++),您可能需要运行nm
工具来获取现有符号名称的列表。
使用-g
选项运行nm
以查看当前导出的符号。
然后,您可以复制工具的输出并将其粘贴到导出文件中,删除任何无关信息。
以下文本显示了由 nm
生成的 Cocoa 框架的一些示例输出:
U .objc_class_name_NSDate
b000ad54 T _InitCocoaFW
b000aea8 T _addNumbers
b000ade8 T _getInitDate
U _objc_msgSend
要导出此输出中指定的框架函数,您需要创建一个包含以下文本的文本文件:
_InitCocoaFW
_addNumbers
_getInitDate
您可以通过在包含符号的行开头放置一个井号来暂时从导出文件中删除该符号。
例如,以下文本会暂时从导出列表中删除_getInitDate
函数:
_InitCocoaFW
_addNumbers
#_getInitDate
2、指定您的 Exports 文件
要在 Xcode 中为您的框架指定导出文件,请执行以下操作:
- 在 Xcode 中打开您的项目。
- 将您的导出文件添加到项目中,并将其放在资源组中。
- 打开框架目标的信息窗口并单击“构建”。
- 将导出的符号文件构建设置设置为导出文件的名称。
您可以从“集合”弹出菜单中选择“全部”,然后在搜索字段中输入其名称来找到此构建设置。
如果您想要导出除受限子集之外的所有符号,则可以使用未导出的符号文件构建设置来实现。
像以前一样创建符号文件,但这次要包含您不想导出的符号列表。
在目标的链接构建设置中,找到未导出的符号文件设置并将其值设置为文件的名称。
如果未导出符号文件构建设置不存在(可能不存在于 v2.2 之前的 Xcode 版本中),则可以使用“其他链接器标志”构建设置。
要隐藏一组符号,请将该构建设置的值设置为以下文本,将exports_filename替换为您的导出文件的名称:
-unexported_symbols_list
导出文件名
十一、安装框架
一旦框架准备就绪,您需要决定将其安装在哪里。
安装框架的位置也有助于确定如何安装框架。
1、公共框架的位置
第三方框架可以位于许多不同的文件系统位置,具体取决于某些因素。
- 大多数公共框架应于
/Library/Frameworks
在地方一级安装。 - 如果您的框架只能由单个用户使用,您可以将其安装在当前用户的
~/Library/Frameworks
子目录中;但是,如果可能,应避免此选项。 - 如果它们要通过局域网使用,它们可以安装在
/Network/Library/Frameworks
;但是,如果可能的话,应该避免这种选择。
几乎在所有情况下,将框架安装在/Library/Frameworks
是最佳选择。
此位置的框架在编译时由编译器自动发现,在运行时由动态链接器自动发现。
链接到其他目录中的框架的应用程序,例如~/Library/Frameworks
或/Network/Library/Frameworks
,必须在构建时指定框架的确切路径,以便动态链接器可以找到它。
如果路径发生变化(就像用户主目录一样),动态链接器可能无法找到框架。
避免在~/Library/Frameworks
或/Network/Library/Frameworks
中安装框架的另一个原因是潜在的性能损失。
安装在网络目录或基于网络的用户主目录中的框架会导致编译时间的显著延迟。
通过网络加载框架也会减慢目标应用程序的启动速度。
第三方框架永远不应安装在/System/Library/Frameworks
目录中。
对该目录的访问受到限制,仅保留给Apple提供的框架。
当您构建应用程序或其他可执行文件时,编译器会在/System/Library/Frameworks
以及编译器指定的任何其他位置查找框架。
编译器会在可执行文件本身中写入任何所需框架的路径信息,以及每个框架的版本信息。
当应用程序运行时,动态链接编辑器会尝试使用可执行文件中的路径找到每个框架的合适版本。
如果它在指定位置找不到合适的框架(可能是因为它被移动或删除了),它会在以下位置查找框架,按此顺序:
- 在链接时指定的框架的显式路径。
/Library/Frameworks
目录。/System/Library/Frameworks
目录。
如果动态链接编辑器无法找到所需的框架,它会生成链接编辑错误,从而终止应用程序。
私有框架的位置
用于内部使用的自定义框架应该安装在使用它们的应用程序中。
嵌入在应用程序中的框架存储在应用程序包的Frameworks
目录中。
以这种方式嵌入框架的优点是它保证应用程序始终具有要运行的框架的正确版本。
有关如何在应用程序中嵌入自定义框架的信息,请参阅Embedding a Private Framework in Your Application Bundle。
嵌入框架的限制是您不能在一套应用程序之间共享框架。
如果您的公司开发了一套依赖于相同框架的应用程序,您可能希望安装所有应用程序可以共享的该框架的一个副本。
在这种情况下,您应该在/Library/Frameworks
目录中安装框架,并确保框架包不包含任何公共标头信息。
3、安装框架
如何安装框架取决于您的框架。
如果您的框架捆绑在特定应用程序内,则您无需执行任何特殊操作。
用户可以将应用程序包拖到本地系统并使用该应用程序,而无需执行任何额外的安装步骤。
如果您的框架位于应用程序外部,则应使用安装包来确保将其放置在正确的位置。
如果存在较旧版本的框架,您也应该使用安装包。
在这种情况下,您可能需要编写一些脚本来更新现有框架包,而不是完全替换它。
例如,您可能希望安装框架的新主要版本而不干扰任何其他版本。
同样,如果您有多个依赖于同一框架的应用程序,则安装包应检查框架是否存在并仅在需要时安装它。
有关创建安装包的更多信息,请参阅在 Mac App Store 之外分发应用程序。
十二、引入框架
对于 OS X 软件开发人员来说,包含头文件和与系统软件链接的指导原则很简单:将框架添加到您的项目中,并在源文件中仅包含顶级头文件。
对于伞形框架,仅包含伞形头文件。
1、在你的项目中包含框架
要将框架包含在 Xcode 项目中,请选择“项目”>“添加到项目”,然后选择框架目录。
或者,您可以按住 Control 键并单击项目组,然后从上下文菜单中选择“添加文件”>“现有框架”。
将现有框架添加到项目时,Xcode 会要求您将其与项目中的一个或多个目标关联。
关联后,Xcode 会自动将框架链接到生成的可执行文件。
**注意:**如果您不使用Xcode来构建您的项目,您必须使用GCC和ld的-framework
选项来构建和链接到指定的框架。有关详细信息,请参阅gcc
和ld
手册页。
您使用#include
指令在代码中包含框架头文件。
如果您在Objective-C中工作,您可以使用#import
指令而不是#include
指令。
这两个指令具有相同的基本结果。但是#import
指令保证同一个头文件永远不会被包含超过一次。包含框架头文件有两种方法:
#include <Framework_name/Header_filename.h>
#import <Framework_name/Header_filename.h>
在这两种情况下,Framework_name
是框架的名称,Header_filename
是该框架或其子框架之一中的头文件的名称。
包含框架头文件时,传统上只包含主框架头文件。
主头文件是名称与框架名称匹配的头文件。
例如,地址簿框架有一个名为AddressBook.h
的主头文件。
要在源代码中包含此头文件,您将使用以下指令:
#include <AddressBook/AddressBook.h>
#import <AddressBook/AddressBook.h>
对于大多数框架,您可以包含主头文件以外的头文件。
您可以包含任何您想要的特定头文件,只要它在框架的Headers
目录中可用。
但是,如果您包含一个伞式框架,您必须包含主头文件。伞式框架不允许您直接包含其组成子框架的头文件。
有关详细信息,请参阅子框架链接上的限制。
2、在非标准目录中定位框架
如果您的项目链接到未包含在任何标准位置中的框架,则必须明确指定该框架的位置,然后 Xcode 才能找到其头文件。
要指定此类框架的位置,请将包含该框架的目录添加到 Xcode 项目的“框架搜索路径”选项中。
Xcode 将此目录列表传递给编译器和链接器,它们都使用该列表来搜索框架资源。
注意: 框架的标准位置是本地系统上的 /System/Library/Frameworks
目录 和/Library/Frameworks
目录。
3、标题和性能
如果您担心包含主头文件可能会导致程序膨胀,请不要担心。
由于 OS X 接口是使用框架实现的,因此这些接口的代码驻留在动态共享库中,而不是可执行文件中。
此外,只有程序使用的代码才会在运行时加载到内存中,因此您的内存占用同样很小。
至于在编译期间包含大量头文件,再次强调,不必担心。
Xcode 提供了预编译头文件功能来加快编译时间。
通过一次编译所有框架头文件,除非您添加新框架,否则无需重新编译头文件。
同时,您可以使用所包含框架中的任何接口,而性能几乎不会受到影响。
4、子框架链接的限制
OS X 包含两种机制,用于确保开发人员仅与伞形框架链接。
一种机制是 Xcode 功能,可防止您选择子框架。
另一种机制是当您尝试包含子框架头文件时发生的编译时错误。
在 Xcode 中,“添加框架”命令会显示/System/Library/Frameworks
中可用的框架。
但是,当您选择其中一个框架时,“打开”对话框会显示有关该框架的信息,而不是子目录列表。
如果您尝试包含子框架中的头文件,Xcode 会生成编译时错误消息。
总括头文件和子框架头文件包含预处理器变量,并会进行检查以防止包含子框架头文件。
如果您使用不正确的#include
语句编译项目,编译器会生成错误消息。
2024-06-15(六)