首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》6.5 从项目到运行应用

关灯直达底部

应用文件实际上是一种特殊的目录,叫作包(package,而特殊的package则叫作bundle)。通常情况下,Finder会将包当作文件,并不会将其内容显示给用户,但你可以绕过这种防护措施并使用Show Package Contents命令查看应用包的内容。这样就可以了解到应用包的结构了。

图6-14:在Finder中查看构建好的应用

我们将使用之前构建的Empty Window应用作为示例应用来一探究竟。你需要在Finder中找到它;默认情况下,它应该位于用户的Library/Developer/Xcode/DerivedData目录下,如图6-14所示。

在Finder中,按下Control键并单击Empty Window应用,从上下文菜单中选择Show Package Contents。你会看到构建过程的结果(如图6-15所示)。

图6-15:应用包的内容

可以将应用包看作项目目录的一种变换:

Empty Window

应用编译后的代码。构建过程会将ViewController.swift与AppDelegate.swift文件编译到这个文件中,即应用的二进制文件。它是应用的核心,实际执行的内容。当应用启动时,该二进制文件会被链接到各种框架上,代码会开始运行(本章后面将会详细介绍“开始运行”所涉及的东西)。

Main.storyboardc

应用的界面故事板文件。项目的Main.storyboard就是应用界面的来源。在该示例中,一个空白视图会占据整个窗口。构建过程会将Main.storyboard编译为更加紧凑的格式(使用ibtool命令行工具),即.storyboardc文件,它实际上包含了多个nib文件,当应用启动时会按需加载。其中一个nib文件会在应用启动时加载进来,它就是界面中所显示的空白视图的来源。Main.storyboardc与项目目录中的Main.storyboard位于同一个子目录中(在Base.lproj中);如前所述,该目录结构与本地化有关(第9章将会介绍)。

LaunchScreen.storyboardc

应用的启动界面文件。该文件(LaunchScreen.storyboard的编译版本)包含了应用启动的短暂时间内所显示的界面。

Assets.car、[email protected]与[email protected]

资源目录与一对图标文件。在构建准备时,我向原来的资源目录Images.xcassets中添加了一些图标图片和其他一些图片资源。该文件处理后(使用actool命令行工具)会生成一个编译后的资源目录文件(.car),它包含了添加到目录中的所有资源。同时,图标文件会被写到应用包的顶层,系统会在这里寻找它们。

Info.plist

这是个遵循严格文本格式的配置文件(属性列表文件)。它来自于项目的Info.plist,但与之并不完全相同。其包含的指令用于告诉系统该如何对待并启动应用。比如,项目的Info.plist有一个计算后的报名,它来自于产品的名字$(PRODUCT_NAME);在构建后的应用的Info.plist中,计算会执行,值会读取Empty Window。此外,它还会连同资源目录一同将图标文件写到应用包的顶层,这时会向构建好的应用的Info.plist中添加一项设置,告诉系统这些图标文件的名字是什么。

Frameworks

有几个框架会添加到构建好的应用中。我们的应用使用了Swift;这些框架会包含完整的Swift语言!应用所用的其他框架会被构建到系统中,但Swift不会。将Swift框架打包到应用包中能够允许Apple快速演化Swift语言,同时又独立于任何系统版本,并且还可以让Swift向后兼容老系统。其副作用就是这些框架会增加应用的大小;不过,相比于Swift的强大与灵活性,这么做是值得的。(也许未来,当Swift语言稳定下来后,它会被构建到系统而不是每个应用中,这样Swift应用就会变得小一些。)

PkgInfo

这是一小段文本,内容是APPL????,表示该应用的类型和创建者代码。PkgInfo文件已经过时了;对于iOS应用的功能来说并没有什么用,并且是自动生成的。你永远不会用到它。

在实际开发中,应用包可能会包含多个文件,但差别主要还是量而不是种类的问题。比如,我们的项目可能会有额外的.storyboard或.xib文件、框架或声音文件等资源。所有这些文件都会按照自己的方式放到应用包当中。此外,在设备上运行的应用包还会包含一些安全相关的文件。

你现在应该能体会到这种项目组件的处理方式以及组装到应用中的方式所带来的好处了,同时也清楚为了确保应用能够正确构建,程序员应该做哪些事情。本节后面的内容将会介绍项目中的哪些内容会被放到应用的构建中,以及应用的构成是如何使得应用能够运行起来的。

6.5.1 构建设置

我们已经介绍了该如何使用构建设置。Xcode本身、项目以及目标都可以修改最终的构建设置值,根据构建配置的不同,其中一些可能会有所不同。在构建前,你需要指定好方案;方案会决定构建配置,这样在构建时特定的构建设置值才会应用上。

6.5.2 属性列表设置

项目中会包含一个属性列表文件,它用于生成构建的应用的Info.plist文件。项目中的这个文件不一定非得叫作Info.plist!应用目标知道该文件是什么,因为其名字位于Info.plist文件的构建设置中。比如,在我们这个项目中,应用目标的Info.plist文件构建设置值被设为了Empty Window/Info.plist(看看构建设置就知道了)。

属性列表文件是个键值对的集合。你可以编辑它,有时也需要这么做。编辑项目的Info.plist主要有3种方式:

·在项目导航器中选中Info.plist文件并在编辑器中对其进行编辑。在默认情况下,键名(以及一些值)会通过一些描述性信息显示,这是由其功能决定的;比如,键名可能用“Bundle name”表示而非实际的键CFBundleName。不过可以通过在编辑器中单击,然后选择Editor→Show Raw Keys & Values或使用上下文菜单来查看实际的键。

此外,可以通过实际的XML格式来查看和编辑Info.plist文件:按住Control并在项目导航器中单击Info.plist文件,并从上下文菜单中选择Open As→Source Code。(不过,以原生XML形式编辑Info.plist是有风险的,因为一旦出错,XML就无效了,这会导致出现问题,但却看不到警告。)

·编辑目标并切换至信息窗格。Custom iOS Target Properties部分会显示出与在编辑器中编辑Info.plist时相同的信息。

·编辑目标并切换至General窗格。这里的一些设置可用于编辑Info.plist。比如,单击Device Orientation复选框会改变Info.plist中“Supported interface orientations”键的值。(这里的其他一些设置可用于编辑构建设置。比如,如果修改了Deployment Target,那就会修改iOS Deployment Target构建设置的值。)

项目Info.plist中的一些值会被处理以将其转换为构建好的应用的Info.plist中的最终值。这个步骤会在构建过程后期进行。比如,项目Info.plist中的“Executable file”键值是$(EXECUTABLE_NAME);它会被EXECUTABLE_NAME构建环境变量的值所替换(可以通过Run Script构建阶段查看它)。此外,在处理过程中还会向Info.plist注入一些额外的键值对。

若想了解键的完整列表及其含义,请参见Apple的文档:Information Property List Key Reference。第9章将会介绍Info.plist中你很有可能会修改的一些设置。

6.5.3  nib文件

nib文件是以一种编译好的格式对用户界面的描述,它包含在一个扩展名为.nib的文件中。你所编写的每个应用都至少包含一个nib文件。通过在Xcode中以图形化方式编辑.storyboard或.xib文件来准备这些nib文件;实际上,你正在设计一些对象,这些对象会在应用运行以及nib文件加载时实例化。

nib文件是在构建过程中生成的,要么通过编辑.xib文件(使用ibtool命令行工具),这会生成一个nib文件;要么通过编辑.storyboard文件,这会生成包含了多个nib文件的.storyboardc包。编辑是对.storyboard或.xib文件进行的,它们位于应用目标的Copy Bundle Resources构建阶段中。

Single View Application模板所生成的Empty Window项目包含了一个名为Main.storyboard的界面.storyboard文件。这个文件会被特殊对待,它是应用的主故事板;之所以是这样并非因为它的名字,而是因为它被Info.plist文件中的键“Main storyboard file base name”(UIMainStoryboardFile)所指向;使用其名字(“Main”)并去掉.storyboard扩展来编辑Info.plist文件就会看到了!结果是,当应用启动时,从.storyboard文件中所生成的第1个nib会自动加载以帮助创建应用的初始界面。

本章后面将会详细介绍应用启动过程与主故事板。请参考第7章以了解关于编辑.storyboard与.xib文件,以及代码运行时它们是如何创建实例的更多信息的。

6.5.4 其他资源

资源是嵌入应用包中的辅助文件,当应用启动时会根据需要获取,比如,想要展示的图片或想要播放的声音等。实际应用很可能会包含多个附加资源。当应用运行时确保这些资源可用通常取决于你的代码(或加载nib文件的代码):基本上,运行时只是进入应用包中,然后拉取所需的资源。实际上,应用包会被看作充满了很多东西的目录。

可以在两个地方向项目添加资源,这对应于两个不同的位置,都位于应用包当中:

项目导航器

如果向项目导航器添加了资源,那么还要确保它会出现在Copy Bundle Resources构建阶段中,它会被构建过程复制到应用包的顶层。在图6-15中,它与图标图像文件处于同一层级,如[email protected]。

图6-16:向项目添加资源时的选项

资源目录

如果向资源目录添加了资源,那么当构建过程向应用包的顶层复制并编译资源目录时(就像图6-15中的Assets.car),资源就会位于其中。

后面将会介绍这两种向项目添加资源的方式。

1.项目导航器中的资源

要想通过项目导航器向项目添加资源,请选择File→Add Files to[Project];还可以将资源从Finder拖曳到项目导航器中。无论哪种方式都会出现一个对话框(如图6-16所示),你可以做如下设置:

目标

你几乎总是应该勾上这个复选框(“Copy items if needed”)。这么做会将资源复制到项目目录中。如果不勾选这个复选框,那么项目就会依赖于项目目录外的文件,你可能会不小心删除或修改它。请将项目所需的一切文件放到项目目录中。

添加目录

只有向项目中添加的是目录时该选项才会起作用;区别在于项目引用目录内容的方式:

创建分组

目录名会成为项目导航器中普通分组的名字;目录内容会出现在该分组中,不过它们会列在Copy Bundle Resources构建阶段中,这样在默认情况下,它们都会被复制到应用包的顶层。

创建目录引用

在项目导航器中,目录显示为蓝色(一个目录引用);此外,它会作为目录显示在Copy Bundle Resources构建阶段中,这意味着构建过程会将整个目录及其内容复制到应用包中。目录中的所有资源都不会位于应用包的顶层,而是在一个子目录中。如果有很多资源,并且想要对其分门别类(而非将所有资源都放在应用包的顶层),或目录层次对于应用是有意义的,那么这种布局就很有价值了。这种布局的副作用就是你所编写的用于访问资源的代码将会特定于包含该资源的目录的子目录。

添加到目标

选中该复选框会将资源添加到目标的Copy Bundle Resources构建阶段中。这样,大多数情况下都需要针对应用目标将其选中;为何需要将资源添加到项目中呢?如果不小心未选中该复选框,稍后发现项目导航器中所列出的资源需要针对某个特定的目标被添加到Copy Bundle Resources构建阶段中,那么你可以手工添加,具体做法如前所述。

2.资源目录中的资源

在Xcode 7之前,资源目录只是用于图片文件。其他资源(如音频文件等)只能在项目导航器中添加。在Xcode 7中,资源目录可以包含任何种类的数据文件。还可以通过资源目录指定一个资源的不同版本以提供给不同硬件配置,比如,设备的屏幕分辨率(对于图片),或iPhone与iPad(对于任何类型的资源)。

对于图片文件来说,资源目录可以帮助你轻松区分图像文件名特殊约定上的差别。比如,由于iOS 9可以运行在一倍分辨率、两倍分辨率及三倍分辨率的设备上,因此需要为每个图片都提供3种尺寸。为了能够与框架的图片加载方法协同工作,这种资源都使用了特殊的命名约定:比如,listen.png、[email protected]与[email protected]。项目导航器中图片文件数量的增长速度会非常快,也很容易出错。资源目录就是为了缓解这个问题而出现的。

相对于在添加到项目时手工单调地命名listen.png文件的多个版本,我可以让资源目录帮助我。编辑资源目录,单击第1列底部的+按钮,选择New Image Set。结果是一个名为Image的图片集,它带有3个不同尺寸的图片。我将图片从Finder拖曳到恰当的地方。原始图片文件名并不重要!图片会被自动复制到项目目录中(位于资源目录中),无须指定这些图片文件的目标成员,因为它们都是资源目录的一部分,已经拥有了正确的目标成员。我可以重命名图片集,将其改为更具描述性的名字,比如,叫它listen。结果是代码现在可以针对当前的屏幕分辨率加载正确的图片,方式是通过"listen"引用它,不用管图片的原始名或扩展是什么。

可以通过选中一张图片,然后使用属性查看器(Command-Option-4)来查看资源目录中的图片。这会显示出原始名与图片的像素大小(这一点更为重要)。

在Xcode 7中,类似的处理过程也适用于其他类型的资源。假设我想要向应用包中添加一个名为Theme.mp3的音频文件。我会编辑资源目录,单击+按钮,并选择New Data Set。这时,一个名为Data的数据集会出现,它有一个Universal位置,我现在就可以将音频文件拖曳进去了。我对数据集进行了重命名(改为了theme);现在,我的代码可以通过名字"theme"来访问该资源了(通过iOS 9新增的NSDataAsset类)。

此外,资源目录中的目录可用于提供命名空间:比如,如果theme数据集位于名为music的资源目录中,如果对该目录勾选上了Provides Namespace in the Attributes inspector选项,那么就可以通过名字"music/theme"来访问该数据集了。

这样,组织就可以通过资源目录约定来使用它们了,而不会因为资源文件搞乱项目导航器与应用包的顶层结构。

在Xcode 7中,应用中的资源可以存储在Apple的服务器上而不必添加到用户从App Store所下载的应用包当中了。代码随后可以在后台下载用户所需的任何资源,并且在不需要时还可以清除。请访问Apple的On-Demand Resources Guide了解更多信息。

6.5.5 代码文件与应用启动过程

构建过程知道要编译什么代码文件以形成应用的二进制文件,这是因为它们都在应用目标的Compile Sources构建阶段中。对于Empty Window项目来说,它们是ViewController.swift与AppDelegate.swift。随着应用开发的进行,你可能会不断向项目添加代码文件,这时要确保它们是目标的一部分,并且位于Compile Sources构建阶段中。经常出现的情况是你想向代码中添加新的对象类型声明;这通常是通过向项目添加新文件来实现的,因为这会使得对象类型声明很容易就能找到,而且Swift的私有性依赖于将代码隔离到不同的文件中(参见第5章)。

在通过File→New→File新建文件时,你可以指定Cocoa Touch Class模板或Swift File模板。Swift File模板只不过是个空白文件而已:它仅仅导入了Foundation框架而已。如果你希望继承某个Cocoa类,那么Cocoa Touch Class模板通常就更为适合了;Xcode会帮你生成初始的类声明,对于某些常见的父类继承来说,如UIViewController与UITableViewController,它甚至提供了一些类方法的桩声明。

当应用启动时,系统知道在应用包的何处寻找二进制文件,因为应用包的Info.plist文件有个“Executable file”键(CFBundleExecutable),其值是二进制文件的文件名;在默认情况下,二进制文件名来自于EXECUTABLE_NAME环境变量(如“Empty Window”)。

1.入口点

应用启动过程中最为复杂的部分就是开始。找到并加载了二进制后,系统必须要调用它,但在哪里调用呢?如果应用是个Objective-C程序,那么答案就是显而易见的。Objective-C是C,因此入口点就是main函数。我们的项目有个main.m文件,它包含了main函数,如以下代码所示:


int main(int argc, char *argv) {    @autoreleasepool {        return UIApplicationMain(argc, argv, nil,            NSStringFromClass([AppDelegate class]));    }}  

main函数只做了两件事:

·创建了内存管理环境:@autoreleasepool与后面的花括号。

·它会调用UIApplicationMain函数,该函数做了很多事情,它会让应用启动并运行。

不过,我们的应用是个Swift程序,它没有main函数!相反,Swift有个特殊的特性:@UIApplicationMain。查看AppDelegate.swift文件,你会看到这个特性,它位于AppDelegate类的声明之上:


@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {  

该特性本质上完成了Objective-C main.m文件所做的全部事情:它会创建一个入口点并调用UIApplicationMain来启动应用。

在某些情况下,你可能想要移除@UIApplicationMain特性并替换为一个main文件,没问题。文件可以是Objective-C文件或Swift文件。假设是个Swift文件。你可以创建一个main.swift文件并确保将其添加到应用委托中。其名字很重要,因为名为main.swift的文件会得到特殊对待:可以将可执行代码放到文件的顶层。文件中应该包含与Objective-C对UIApplicationMain的调用相当的代码:


import UIKitUIApplicationMain(    Process.argc, Process.unsafeArgv, nil, NSStringFromClass(AppDelegate))  

为什么要这么做呢?大概是因为你想要在main.swift文件中做些其他事情,或想要定制对UIApplicationMain的调用。

2.UIApplicationMain

无论是编写自己的main.swift文件还是使用[email protected]特性都会调用UIApplicationMain。这个函数调用是应用要做的一件重要事情。整个应用除了调用UIApplicationMain,就没别的了!此外,UIApplicationMain负责解决应用运行时会遇到的一些棘手问题。应用在何处创建最初的实例呢?一开始会调用这些实例上的哪些实例方法呢?应用的初始界面来自于何处?下面来看看UIApplicationMain到底做了哪些事情:

1.UIApplicationMain会创建应用的首个实例,即共享的应用实例,随后可以通过调用UIApplication.sharedApplication()来访问该实例。对UIApplicationMain调用的第3个参数(一个字符串)指定了该共享应用实例是哪个类的实例。如果该参数为nil(通常情况下均如此),那么默认类就是UIApplication。如果不为nil,那就需要继承UIApplication,这时需要将子类替换为一个显式的值,比如,NSStringFromClass(MyApplicationSubclass,取决于调用的子类),并将其作为第3个参数来调用UIApplicationMain。

2.UIApplicationMain还会创建应用的第2个实例,即应用实例的委托。委托是一种重要且应用广泛的Cocoa模式,第11章将会对其进行详细介绍。它非常重要,你所编写的每个应用都会有一个应用委托实例。对UIApplicationMain调用的第4个参数(是个字符串)指定了应用委托实例是什么类。在手工版本的main.swift中,它就是NSStringFromClass(AppDelegate)。如果使用@UIApplicationMain特性,那么在默认情况下,该特性会被放到AppDelegate.swift中的AppDelegate类声明之上;该特性表示:“这是应用委托类。”

3.如果Info.plist文件指定了主故事板文件,那么UIApplicationMain就会加载它并寻找故事板的初始视图控制器(或是故事板的入口点);它会实例化该视图控制器,从而创建应用的第3个实例。对于我们的Empty Window项目,它由Single View Application模板创建,该视图控制器是名为ViewController的类实例;定义了该类以及ViewController.swift的代码文件也是由该模板创建的。

4.如果有主故事板文件,那么UIApplicationMain现在就会创建应用的窗口,这是应用的第4个实例,即UIWindow的实例(或者,应用委托可以替换UIWindow子类的实例)。它会将该窗口实例作为应用委托的window属性;它还会将初始的视图控制器实例作为窗口实例的rootViewController属性。该视图现在是应用的根视图控制器。

5.UIApplicationMain现在转向了应用委托实例并开始调用它的一些代码,如application:didFinishLaunchingWithOptions:。自定义代码可以借此机会运行!application:didFinishLaunchingWithOptions:就是放置用于初始化值以及执行启动任务的代码的绝佳之处;不过,请不要将耗费时间的代码放置在这里,因为这时应用的界面尚未出现。

6.如果有主故事板,那么UIApplicationMain现在就可以让应用界面出现了。这是通过调用UIWindow的实例方法makeKeyAndVisible实现的。

7.现在窗口要出现了。这反过来又会导致窗口转向根视图控制器,并告诉它获取其主视图,它会出现并占据窗口。如果视图控制器从.storyboard或.xib文件获取视图,那么相应的nib文件就会加载进来;其对象会被实例化和初始化,它们会成为初始界面的对象:视图会被放到窗口中,它与子视图会对用户可见。视图控制器的viewDidLoad这时也会被调用——这是你的代码提前开始运行的另一个时机。

应用现在就会启动并运行!它有一组初始实例,至少有共享应用实例、窗口、初始视图控制器与初始视图控制器的视图,以及它所包含的界面对象。你的一些代码已经开始运行:UIApplicationMain依旧在运行(它永远不会返回),就在那儿,注视着用户的一举一动,维护着事件循环,而事件循环会在用户动作发生时对其做出响应。

3.没有故事板的应用

在对应用启动过程的描述中,我使用几次短语“如果有主故事板”。在Xcode 7应用模板中,比如,用于生成Empty Window项目的Single View Application模板,有一个主故事板。不过,也可以没有主故事板。在这种情况下,如创建窗口实例、为其赋予根视图控制器、将其赋给应用委托的window属性,以及调用窗口的makeKeyAndVisible来显示界面等,都需要通过代码来实现。

为了说明这一点,请通过Single View Application模板新建一个iPhone项目,叫作Truly Empty。然后按照如下步骤进行:

1.编辑目标。在General窗格中,选择Main Interface域中的“Main”,然后将其删除(并按下Tab键使之生效)。

2.从项目中删除Main.storyboard与ViewController.swift。

3.选中并删除AppDelegate.swift中的所有代码。

现在的项目有一个应用委托,但却没有故事板,没有代码!为了创建一个最小可运行的应用,你需要按照这种方式编辑AppDelegate.swift来重新创建AppDelegate类,只保留创建和显示窗口的代码,如示例6-1所示。

示例6-1:没有故事板的应用委托类


import UIKit@UIApplicationMainclass AppDelegate : UIResponder, UIApplicationDelegate {    var window : UIWindow?    func application(application: UIApplication,        didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)        -> Bool {            self.window = UIWindow            self.window!.rootViewController = UIViewController            self.window!.backgroundColor = UIColor.whiteColor            self.window!.makeKeyAndVisible            return true    }}  

这会生成一个可运行的最小应用,它有一个空白窗口;可以通过将窗口的backgroundColor改为其他颜色(如UIColor.redColor())并再次运行应用来验证创建窗口的代码的正确性。

这是个可运行的应用,不过却没什么用。它什么都没做,也做不了,因为其根视图控制器是通用的UIViewController。我们这里需要的是自己的视图控制器实例(包含自己的代码),可以在nib中配置的视图。下面来创建一个UIViewController子类以及包含其视图的.xib文件:

1.选择File→New→File。在“Choose a template”对话框中,iOS下面,单击左侧的Source,然后选择Cocoa Touch Class。单击Next。

2.将类命名为MyViewController,指定它为UIViewController的子类。勾选“Also create XIB file”复选框。指定语言为Swift。单击Next。

3.这时会弹出Save对话框。请确保将文件保存到Truly Empty目录中、将Group弹出菜单设为Truly Empty,并勾选Truly Empty目标,这些文件会成为应用目标的组成部分。单击Create。

Xcode已经为我们创建了两个文件:MyViewController.swift(将MyViewController作为UIViewController的子类)与MyViewController.xib(nib的源,MyViewController实例会在这里获得其视图)。

4.在AppDelegate.swift中,回到应用委托的application:didFinishLaunchingWithOptions:,将根视图控制器的类修改为MyViewController,并将其关联到nib,如以下代码所示:


self.window!.rootViewController =    MyViewController(nibName:"MyViewController", bundle:nil)  

我们现在没有使用故事板创建了一个可用且最小的应用项目。代码完成了存在主故事板的情况下UIApplicationMain所自动完成的一些工作:我们实例化了UIWindow,将窗口实例设为了应用委托的window属性;实例化了一个初始视图控制器,让窗口能够出现。此外,窗口的出现会自动导致MyViewController实例从MyViewController.xib编译好的nib中获取到其视图;这样,我们可以通过MyViewController.xib来自定义应用的初始界面。除了说明UIApplicationMain隐式所做的事情,这也是一种构建应用的合理方式。

6.5.6 框架与SDK

框架就是你的代码所用的编译好的代码库。在进行iOS编程时,你所用的大多数框架都是Apple内建的框架。这些框架已经成为应用运行的设备系统的一部分了,位于/System/Library/Frameworks下,但在iPhone或iPad上就没法指定了,因为我们没法(通常情况下如此)直接查看文件的层次结构。

在构建项目并在电脑上运行时,编译好的代码还需要连接到这些框架上。为了达成所愿,iOS设备的System/Library/Frameworks在电脑上会有个副本,就在Xcode中。这种设备系统的副本子集称作SDK(即“软件开发包”)。到底使用哪个SDK取决于构建目标是什么。

链接指的是将编译好的代码与所需框架连接起来的过程,这些框架在构建期位于一个地方,但在运行期却在另一个地方。比如:

当构建代码以在设备上运行时

会使用所需框架的副本。该副本位于iPhone SDK的System/Library/Frameworks中,它在Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk中。

当代码在设备上运行时

代码开始运行时会在设备顶层目录/System/Library/Frameworks中查找所需框架。

通过框架这种方式,当应用运行时,Apple的代码就会动态合并到你的应用中。框架是每个应用所需的东西的集散地;它们就是Cocoa。内容有很多,编译好的代码也有很多。应用会共享框架的精华与功能,因为应用会链接到框架上。代码运行时就好像框架代码是其一部分一样。不过,应用只会完成很少的一部分工作;框架才是真正的能量之源。

链接会负责将编译好的代码连接到所需的框架上,不过这还不足以让代码编译通过。框架中有很多你的代码会调用的类(如NSString)与方法(如rangeOfString:)。为了满足编译器的要求,框架会在其头文件中发布API,代码则会导入框架头文件。比如,代码可以使用NSString并调用rangeOfString:,这是因为它导入了NSString头文件。事实上,你的代码所导入的是UIKit头文件,它反过来又导入了Foundation头文件,而Foundation又导入了NSString头文件。可以在自己代码的头文件中看到这一点:


import UIKit  

按住Command键并单击UIKit会转到Swift的UIKit头文件中。文件顶部是import Foundation。查看Foundation头文件并向下滚动,你会看到import Foundation.NSString。查看NSString头文件,你会看到rangeOfString:方法的声明。

这样,使用框架需要两步:

导入框架的头文件

代码需要这个信息才能成功编译。代码会通过import关键字导入框架的头文件,从而导入框架,或导入的框架再导入所需的框架。在Swift中,可以通过模块名指定框架。

链接到框架

运行时,编译后的可执行二进制文件需要连接到所用的框架上,这会将编译后的代码与这些框架合并起来。代码构建完毕后,它会链接到所需的框架上,按照目标Link Binary With Libraries构建阶段所列出的框架进行。

不过,我们的项目并没有任何显式的链接。查看应用目标的Link Binary With Libraries构建阶段,你会发现它是空的。这是因为Swift使用了模块,模块可以进行自动链接。在Objective-C中,这两个特性都是可选的,并且由构建设置管理。不过在Swift中,模块与自动链接的使用则是自动的。

模块是存储在电脑Library/Developer/Xcode/DerivedData/ModuleCache中的缓存信息。仅仅打开一个Swift项目就会使得任何被导入的模块都缓存在那里。进入ModuleCache目录中,你会看到大量框架与头文件(.pcm文件)所构成的模块。Swift对模块的使用简化了导入与链接的过程,并加快了编译时间。

模块是精巧且便捷的,不过有时还需要手工链接到框架上。比如,假设你想在界面中使用MKMapView(Map Kit View)。你可以在.storyboard或.xib文件中配置,不过当构建和运行应用时,应用会崩溃,消息是“Could not instantiate class named MKMapView.”。原因在于nib在加载时会发现它包含了一个MKMapView,但却不知道MKMapView是什么。MKMapView定义在MapKit框架中,但nib并不知道这一点。

在代码顶部加上import MapKit也无法解决这个问题;如果代码想要使用MKMapView,你就需要这么做,不过这却没办法在nib加载时让其知道何为MKMapView。解决办法就是手工链接到MapKit框架:

1.编辑目标,查看构建阶段窗格。

2.在Link Binary With Libraries下单击+按钮。

3.这时会出现一个可用框架列表(还有一些动态库)。向下滚动到MapKit.framework,将其选中并单击Add。

这可以解决问题:应用现在可以构建并运行了。

你还可以创建自己的框架并作为项目的一部分。框架是个模块,因此也有助于结构化你的代码,正如第5章介绍Swift隐私性时所述。要想创建新的框架:

1.编辑目标并选择Editor→Add Target。

2.在对话框左侧,iOS下,选择Framework & Library;在右侧,选择Cocoa Touch Framework,单击Next。

3.为框架取个名字;叫它Coolness。你可以选择语言,不过我不确定这么做是否有用,因为现在还没有创建任何代码文件。默认情况下,应用弹出菜单中的Project and Embed应该已经被正确设定好了,单击Finish。

这时,项目中会创建好一个新的Coolness框架目标。如果向Coolness目标添加一个.swift文件,并在里面定义一个对象类型,将其声明为public;回到一个主应用目标文件上,如AppDelegate.swift,那么代码就可以import Coolness,并且能够看到该对象类型及里面的公共成员了。