Resource Programming Guide (一)

关于资源

适用于计算机程序的资源是与程序可执行代码相关的数据文件。资源可以通过将代码之外的复杂数据集或图形内容创建到更合适的工具中来简化您必须编写的代码。例如,使用代码逐个像素地创建图像,在图像编辑器中创建它们的效率(和实用性)要高得多。为了利用资源,所有的代码都必须在运行时加载它并使用它。

除了简化代码之外,资源也是所有应用程序的国际化流程的一个亲密的部分。您可以将应用程序中的字符串和其他用户可见内容硬编码,而不是将内容放在外部资源文件中。本地化您的应用程序然后成为为每种支持的语言创建每个资源文件的新版本的简单过程。在OS X和iOS中使用的捆绑机制提供了组织本地化资源并促进加载与用户首选语言匹配的资源文件的方式。

本文档提供有关OS X和iOS支持的资源类型的信息,以及如何在代码中使用这些资源。本文档不关注资源创建过程。大多数资源是使用第三方应用程序或/ Developer / Applications目录中提供的开发人员工具创建的。此外,虽然本文档涉及应用程序中的资源使用,但是该信息也适用于其他类型的捆绑可执行文件,包括框架和插件。

在阅读本文档之前,您应该熟悉应用程序包强加的组织结构。了解此结构可以更轻松地组织和查找应用程序使用的资源文件。有关捆绑结构的信息,请参阅捆绑编程指南。

概述

应用程序可以包含许多类型的资源,但有几种可由iOS和OS X直接支持。

Nib文件存储应用程序用户界面的对象

Nib文件是用于创建iOS和Mac应用程序的典型资源类型。 nib文件是一个数据存档,其基本上包含要在运行时重新创建的一组冻干对象。 Nib文件最常用于存储预配置的窗口,视图和其他面向视觉的对象,但它们也可以存储非可视对象(如控制器)。

您可以使用Interface Builder编辑Xcode中的nib文件,该文件提供了组装对象的图形编辑器。当您随后将nib文件加载到应用程序中时,nib加载代码会实例化文件中的每个对象,并将其还原到您在Interface Builder中指定的状态。因此,您在Interface Builder中看到的确实是您在运行时在应用程序中获得的。

相关章节:Nib文件

包含可本地化文本的字符串资源

文本是大多数用户界面的突出部分,也是受本地化更改影响最大的资源。 iOS和OS X不是将文本硬编码到代码中,而是支持在字符串文件中存储用户可见文本,这些文本文件包含用于应用程序的一组字符串资源的人机阅读文本文件(UTF-16编码) 。 (使用多个“字符串”是有意的,并且由于该类型的文件使用的.strings文件扩展名。)字符串文件大大简化了国际化和本地化过程,允许您编写一次代码,然后正确加载来自资源文件的本地化文本可以轻松更改。

Core Foundation和Foundation框架提供了从字符串文件加载文本的功能。使用这些工具的应用程序也可以利用Xcode附带的工具在整个开发过程中生成和维护这些资源文件。

相关章节:字符串资源

图像,声音和电影表示预呈现的内容

图像,声音和电影资源在iOS和Mac应用程序中发挥重要作用。图像负责创建每个操作系统使用的独特视觉风格;它们还帮助简化复杂视觉元素的绘图代码。声音和电影文件同样有助于增强应用程序的整体用户体验,同时简化创建体验所需的代码。这两种操作系统都为您的应用程序中加载和呈现这些类型的资源提供了广泛的支持。

相关章节:图像,声音和视频资源

属性列表和数据文件将数据与代码分开

属性列表文件是用于存储字符串,数字,布尔值,日期和原始数据值的结构化文件。使用数组和字典结构组织文件中的数据项,大多数项与唯一键相关联。系统使用属性列表来存储简单的数据集。例如,几乎所有应用程序中的Info.plist文件都是属性列表文件的示例。您还可以使用属性列表文件进行简单的数据存储需求。

除了属性列表之外,OS X还支持特定用途的一些特殊结构化文件。例如,AppleScript数据和用户帮助使用特殊格式的数据文件进行存储。您还可以创建自己的自定义数据文件。

相关章节:数据资源文件

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的映像。

也可以看看

以下Apple Developer文档在概念上与资源编程指南有关:

“捆绑编程指南”介绍了应用程序用于存储可执行代码和资源的捆绑结构。
国际化和本地化指南描述了准备应用程序(及其资源)以翻译成其他语言的过程。
章节在Xcode中构建用户界面概述描述了编辑nib文件资源的工具。
“属性列表编程指南”介绍了将属性列表资源文件加载到Cocoa应用程序中的工具。
Core Foundation的属性列表编程主题描述了将属性列表资源文件加载到基于C的应用程序中的工具。

Nib文件

Nib文件在OS X和iOS中创建应用程序中起着重要的作用。使用nib文件,您可以使用Xcode(而不是以编程方式)以图形方式创建和操作用户界面。因为您可以立即查看更改的结果,您可以快速尝试不同的布局和配置。您也可以稍后更改用户界面的许多方面,而无需重写任何代码。

对于使用AppKit或UIKit框架构建的应用程序,nib文件具有重要意义。这两个框架都支持使用nib文件来布局窗口,视图和控件,并将这些项目与应用程序的事件处理代码集成在一起。 Xcode与这些框架配合使用,可帮助您将用户界面的控件连接到项目中对这些控件进行响应的对象。该集成显着减少了加载nib文件后所需的设置量,并且稍后可以轻松更改代码和用户界面之间的关系。

注意:尽管您可以在不使用nib文件的情况下创建Objective-C应用程序,但这样做非常罕见,不推荐使用。根据您的应用程序,避免nib文件可能需要您替换大量的框架行为才能实现与使用nib文件相同的结果。

Nib文件的解剖

一个nib文件描述了应用程序的用户界面的视觉元素,包括窗口,视图,控件等等。它还可以描述非可视元素,例如应用程序中管理窗口和视图的对象。最重要的是,nib文件与Xcode中的配置完全相同。在运行时,这些描述用于在应用程序中重新创建对象及其配置。当您在运行时加载nib文件时,您将获得Xcode文档中对象的精确副本。 nib加载代码实例化对象,配置它们,并重新建立您在nib文件中创建的任何对象间连接。

以下部分描述了如何组织与AppKit和UIKit框架一起使用的nib文件,在其中找到的对象类型以及如何有效地使用这些对象。

关于您的接口对象

接口对象是添加到nib文件来实现用户界面的对象。当在运行时加载一个笔尖时,接口对象是由nib加载代码实际实例化的对象。大多数新的nib文件默认情况下至少有一个接口对象,通常是窗口或菜单资源,并且您可以在界面设计中添加更多的接口对象到nib文件。这是nib文件中最常见的对象类型,通常是为什么首先创建nib文件。

除了表示视觉对象,如窗口,视图,控件和菜单之外,界面对象还可以表示非视觉对象。在几乎所有情况下,添加到nib文件的非可视对象都是您的应用程序用于管理可视对象的额外控制器对象。虽然您可以在应用程序中创建这些对象,但将其添加到nib文件并将其配置在那里通常更为方便。 Xcode提供了一个通用对象,您可以在将控制器和其他非可视对象添加到nib文件时特别使用。它还提供了通常用于管理Cocoa绑定的控制器对象。

关于文件的所有者

nib文件中最重要的对象之一是File的Owner对象。与接口对象不同,文件所有者对象是一个占位符对象,在加载nib文件时不会创建。相反,您可以在代码中创建此对象,并将其传递给nib加载代码。这个对象是如此重要的原因是它是应用程序代码和nib文件内容之间的主要链接。更具体地说,它是负责nib文件内容的控制器对象。

在Xcode中,您可以在文件的所有者和nib文件中的其他接口对象之间创建连接。加载nib文件时,nib加载代码将使用您指定的替换对象重新创建这些连接。这允许您的对象引用nib文件中的对象并自动从接口对象接收消息。

关于第一个响应者

在nib文件中,First Responder是一个占位符对象,表示应用程序动态确定的响应器链中的第一个对象。因为应用程序的响应者链无法在设计时确定,所以第一响应者占位符充当需要针对应用程序的响应者链的任何操作消息的备用目标。菜单项通常针对第一响应者占位符。例如,“窗口”菜单中的“最小化”菜单项隐藏应用程序中的最前面的窗口,而不仅仅是特定的窗口,“复制”菜单项应该复制当前选择,而不仅仅是单个控件或视图的选择。应用程序中的其他对象也可以针对第一响应者。

当您将nib文件加载到内存中时,您无需管理或替换第一个响应程序占位符对象。 AppKit和UIKit框架根据应用程序的当前配置自动设置和维护第一个响应者。

有关响应者链的更多信息以及如何用于在基于AppKit的应用程序中分派事件,请参阅“事件处理指南”中的事件体系结构。有关iPhone应用程序中的响应者链和处理操作的信息,请参阅UIKit应用程序的事件处理指南。

关于顶级对象

当您的程序加载nib文件时,Cocoa会重新创建Xcode中创建的对象的整个图形。该对象图包括在nib文件中找到的所有窗口,视图,控件,单元格,菜单和自定义对象。顶级对象是不具有父对象的这些对象的子集。顶级对象通常仅包含添加到nib文件中的窗口,菜单栏和自定义控制器对象。 (诸如文件所有者,第一响应者和应用程序的对象是占位符对象,并不被视为顶级对象。)

通常,您使用文件的所有者对象中的插座来存储对nib文件的顶级对象的引用。但是,如果不使用插座,则可以直接从nib加载例程检索顶层对象。您应该始终保持一个指向这些对象的指针,因为您的应用程序负责释放它们后,使用它们。有关加载时的nib对象行为的更多信息,请参阅从Nib文件管理对象的生命周期。

关于图像和声音资源

在Xcode中,您可以从nib文件的内容中引用外部图像和声音资源。一些控件和视图能够显示图像或播放声音作为默认配置的一部分。 Xcode库提供对Xcode项目的图像和声音资源的访问,以便您可以将nib文件链接到这些资源。 nib文件不直接存储这些资源。相反,它存储资源文件的名称,以便稍后可以找到nib加载代码。

加载包含图像或声音资源引用的nib文件时,nib加载代码将实际的图像或声音文件读入内存并缓存它。在OS X中,图像和声音资源存储在命名的缓存中,以便以后可以在需要时访问它们。在iOS中,只有映像资源存储在命名缓存中。要访问图像,请使用NSImage或UIImage的imageNamed:方法,具体取决于您的平台。要在OS X中访问缓存的声音,请使用NSSound的soundNamed:方法。

Nib文件设计指南

创建nib文件时,请仔细考虑如何打算使用该文件中的对象。一个非常简单的应用程序可能能够将所有用户界面组件存储在单个nib文件中,但对于大多数应用程序,最好将组件分布在多个nib文件中。创建较小的nib文件可让您立即加载您需要的界面部分。它们还可以更容易地调试您可能遇到的任何问题,因为找不到问题的地方较少。

创建nib文件时,请牢记以下准则:

设计你的nib文件与懒加载。计划加载仅包含您需要的对象的nib文件。
在OS X应用程序的主要nib文件中,请考虑仅将应用程序菜单栏和可选应用程序委托对象存储在nib文件中。避免在应用程序启动后包括任何不会使用的窗口或用户界面元素。相反,将这些资源放在单独的nib文件中,并在启动后根据需要加载它们。
将重复的用户界面组件(如文档窗口)存储在单独的nib文件中。
对于仅偶尔使用的窗口或菜单,将其存储在单独的nib文件中。通过将其存储在单独的nib文件中,只有在实际使用时才将资源加载到内存中。
使文件所有者成为nib文件外的任何单一联系人;请参阅访问Nib文件的内容。

Nib对象生命周期

当nib文件加载到内存中时,nib加载代码需要几个步骤来确保nib文件中的对象被正确创建和初始化。了解这些步骤可以帮助您编写更好的控制器代码来管理用户界面。

对象加载过程

当您使用NSNib或NSBundle的方法加载和实例化nib文件中的对象时,底层的nib加载代码执行以下操作:

它将nib文件和任何引用的资源文件的内容加载到内存中:
整个nib对象图的原始数据被加载到内存中,但不是未归档。
与nib文件相关联的任何自定义图像资源都将被加载并添加到Cocoa图像缓存中;请参阅关于图像和声音资源。
与nib文件相关联的任何自定义声音资源都被加载并添加到Cocoa声音缓存;请参阅关于图像和声音资源。
它取消归档nib对象图数据并实例化对象。如何初始化每个新对象取决于对象的类型及其在归档中的编码方式。 nib加载代码使用以下规则(按顺序)来确定要使用的初始化方法。
默认情况下,对象会收到一个initWithCoder:消息。
在OS X中,标准对象列表包括系统提供的视图,单元格,菜单和视图控制器,并且在默认的Xcode库中可用。它还包括使用自定义插件添加到库的任何第三方对象。即使您更改了这样一个对象的类,Xcode将标准对象编码到nib文件中,然后在对象被取消存档时通知归档器在自定义类中交换。

在iOS中,使用initWithCoder:方法初始化符合NSCoding协议的任何对象。这包括UIView和UIViewController的所有子类,无论它们是默认的Xcode库或定义的自定义类的一部分。

OS X中的自定义视图会收到一条initWithFrame:消息。
自定义视图是NSView的子类,Xcode没有可用的实现。通常,这些是您在应用程序中定义并用于提供自定义可视内容的视图。自定义视图不包括作为默认库或集成第三方插件的一部分的标准系统视图(如NSSlider)。

当它遇到自定义视图时,Xcode将一个特殊的NSCustomView对象编码到你的nib文件中。自定义视图对象包括构建您指定的真实视图子类所需的信息。在加载时,NSCustomView对象将一个alloc和initWithFrame:消息发送到真实的视图类,然后交换自己生成的视图对象。净效果是真正的视图对象处理在nib加载过程中的后续交互。

iOS中的自定义视图不会使用initWithFrame:方法进行初始化。

除了上述步骤中描述的以外的自定义对象接收初始消息。

它重新建立nib文件中对象之间的所有连接(动作,插座和绑定)。这包括与文件所有者和其他占位符对象的连接。建立连接的方法因平台而异:
出口连接
在OS X中,笔尖加载代码首先尝试使用对象自己的方法重新连接插座。对于每个出口,Cocoa查找一个形式为setOutletName的方法,如果存在这样的方法,则调用它。如果找不到这样的方法,Cocoa会在对象中搜索具有相应插座名称的实例变量,并尝试直接设置值。如果找不到实例变量,则不会创建任何连接。
设置出口还会为任何注册的观察者生成键值观察(KVO)通知。这些通知可能在所有对象间连接重新建立之前发生,并且在调用对象的任何awakeFromNib方法之前肯定会发生这些通知。

在iOS中,nib加载代码使用setValue:forKey:方法重新连接每个出口。该方法类似地寻找适当的访问器方法,并且在失败时落后于其他方式。有关此方法如何设置值的更多信息,请参阅其在NSKeyValueCoding协议参考中的描述。
在iOS中设置出口还会为任何注册的观察者生成KVO通知。这些通知可能在所有对象间连接重新建立之前发生,并且在调用对象的任何awakeFromNib方法之前肯定会发生这些通知。

动作连接

在OS X中,nib加载代码使用源对象的setTarget:和setAction:方法来建立与目标对象的连接。如果目标对象没有响应action方法,则不会创建任何连接。如果目标对象为零,则该动作由响应者链处理。
在iOS中,nib加载代码使用UIControl对象的addTarget:action:forControlEvents:方法来配置操作。如果目标为零,则动作由响应者链处理。

绑定

在OS X中,Cocoa使用源对象的bind:toObject:withKeyPath:options:方法来创建它与其目标对象之间的连接。
iOS中不支持绑定。
它将一个awakeFromNib消息发送到nib文件中定义匹配选择器的相应对象:
在OS X中,该消息被发送到定义该方法的任何接口对象。它也被发送到文件的所有者和定义它的任何占位符对象。
在iOS中,此消息仅发送到由nib加载代码实例化的接口对象。它不发送到文件的所有者,第一响应者或任何其他占位符对象。
它显示在nib文件中启用了“启动时可见”属性的任何窗口。
nib加载代码调用对象的awakeFromNib方法的顺序是不能保证的。在OS X中,Cocoa尝试最后调用File的Owner的awakeFromNib方法,但不能保证该行为。如果您需要在加载时进一步配置nib文件中的对象,则最适合的时间是在您的nib加载调用返回后。在这一点上,所有的对象都被创建,初始化并准备好使用。

从Nib文件管理对象的生命周期

每次您要求NSBundle或NSNib类加载nib文件时,底层代码会创建该文件中的对象的新副本并将其返回给您。 (nib加载代码不会从以前的加载尝试中回收nib文件对象。)您需要确保在必要时保持新的对象图,并在完成之后将其取消。通常需要对顶级对象的强引用以确保它们不被释放;您不需要强烈引用图表中较低的对象,因为它们由父母拥有,您应该尽可能减少创建强引用周期的风险。

从实际的角度来看,iOS和OS X的出口应该被定义为声明的属性。 Outlets通常应该是弱的,除了那些从File的所有者到顶级对象的nib文件(或在iOS,一个故事板场景中)应该是强的。 因此,您创建的出口通常应该很弱,因为:

例如,您创建到视图控制器视图或窗口控制器窗口的子视图的出口是不暗示所有权的对象之间的任意引用。
强大的插座经常由框架类指定(例如,UIViewController的视图插座或NSWindowController的窗口)。

@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;

注意:在OS X中,并非所有类都支持弱引用 - 请参阅转换到ARC发行说明。 在不能指定的情况下,您应该使用

assign:
@property (assign) IBOutlet NSTextView *textView;

出口通常被认为是定义类别的私人; 除非有理由公开揭露属性,否则隐藏属性声明类扩展名。 例如:

// MyClass.h

@interface MyClass : MySuperclass
@end

// MyClass.m

@interface MyClass ()
@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;
@end

这些模式扩展到从容器视图到其子视图的引用,您必须考虑对象图的内部一致性。例如,在表视图单元格的情况下,特定子视图的出口通常应该通常较弱。如果表视图包含图像视图和文本视图,那么这些视图仍然有效,只要它们是表视图单元本身的子视图。

当出口被认为拥有参考对象时,出口应变为强:

如前所述,通常情况下,文件的所有者级别的对象在nib文件中经常被认为是由文件所有者拥有的。
在某些情况下,您可能需要一个来自nib文件的对象存在其原始容器之外。例如,您可能有一个视图的出口,可以临时从其初始视图层次结构中删除,因此必须独立进行维护。
您希望被子类化的类(特别是抽象类)公开暴露出来,以便它们可以被子类(例如UIViewController的视图插件)适当地使用。如果期望消费者需要与物业相互作用,则出口也可能会暴露出来;例如,表视图单元格可能会暴露子视图。在后一种情况下,公开一个以私有重新定义的只读公共出口可能是可读的,例如:

// MyClass.h

@interface MyClass : UITableViewCell
@property (weak, readonly) MyType *outletName;
@end

// MyClass.m

@interface MyClass ()
@property (weak, readwrite) IBOutlet MyType *outletName;
@end

OS X中的顶级对象可能需要特殊处理
由于历史原因,在OS X中,将创建一个nib文件中的顶级对象,并附加一个引用计数。应用套件提供了几个功能,可帮助确保正确释放nib对象:

NSWindow对象(包括面板)有一个isReleasedWhenClosed属性,如果设置为YES,则会在窗口关闭时指示窗口释放自身(以及其视图层次结构中的所有相关对象)。在nib文件中,您可以通过Xcode检查器的“属性”窗格中的“释放时关闭”复选框来设置此选项。
如果文件的nib文件的所有者是NSWindowController对象(在基于文档的应用程序中的文档nib中的默认值),请记住NSDocument管理NSWindowController的一个实例)或NSViewController对象,它会自动处理其管理的窗口。
如果文件的所有者不是NSWindowController或NSViewController的实例,那么您需要自己递减顶级对象的引用计数。您必须将顶级对象的引用转换为Core Foundation类型并使用CFRelease。 (如果您不希望有所有顶级对象的插座,可以使用NSNib类的instantiateNibWithOwner:topLevelObjects:方法来获取一个nib文件顶级对象的数组。)

行动方法

一般来说,操作方法(参见OS X中的目标操作或iOS中的目标操作)是通常由nib文件中另一个对象调用的方法。 Action方法使用类型限定符IBAction,它用于代替void返回类型,将声明的方法标记为一个操作,以便Xcode知道它。

@interface MyClass
- (IBAction)myActionMethod:(id)sender;
@end

您可以选择将操作方法视为对您的类是私有的,因此不会在public @interface中声明它们。 (因为Xcode解析实现文件,所以不需要在标题中声明它们。)

// MyClass.h

@interface MyClass
@end

// MyClass.m

@implementation MyClass
- (IBAction)myActionMethod:(id)sender {
    // Implementation.
}
@end

您通常不应以编程方式调用操作方法。 如果您的类需要执行与action方法相关联的工作,那么您应该将实现应用到另一种由action方法调用的方法中。

// MyClass.h

@interface MyClass
@end

// MyClass.m

@interface MyClass (PrivateMethods)
- (void)doSomething;
- (void)doWorkThatRequiresMeToDoSomething;
@end

@implementation MyClass
- (IBAction)myActionMethod:(id)sender {
    [self doSomething];
}

- (void)doSomething {
    // Implementation.
}

- (void)doWorkThatRequiresMeToDoSomething {
    // Pre-processing.
    [self doSomething];
    // Post-processing.
}

@end

内置支持Nib文件

AppKit和UIKit框架都提供了一定量的自动化行为来加载和管理应用程序中的nib文件。这两个框架都提供了用于加载应用程序主要nib文件的基础设施。此外,AppKit框架提供了通过NSDocument和NSWindowController类加载其他nib文件的支持。以下部分介绍了nib文件的内置支持,如何利用它们,以及如何在自己的应用程序中修改该支持。

应用程序加载主Nib文件

应用程序的大多数Xcode项目模板都已预先配置了一个主要的nib文件已经到位。所有您需要做的是修改nib文件中的默认nib文件并构建您的应用程序。在启动时,应用程序的默认配置数据告诉应用程序对象找到该nib文件,以便它可以加载它。在基于AppKit和UIKit的应用程序中,此配置数据位于应用程序的Info.plist文件中。首次加载应用程序时,默认应用程序启动代码会在Info.plist文件中查找NSMainNibFile密钥。如果找到它,它会在应用程序包中查找一个nib文件,其名称(带或不带文件扩展名)与该键的值匹配并加载它。

每个视图控制器管理其自己的Nib文件

UIViewController(iOS)和NSViewController(OS X)类支持自动加载其关联的nib文件。如果在创建视图控制器时指定nib文件,则当您尝试访问视图控制器的视图时,该nib文件会自动加载。视图控制器和nib文件对象之间的任何连接都将自动创建,在iOS中,当视图最终加载并显示在屏幕上时,UIViewController对象还会收到其他通知。为了更好地管理内存,UIViewController类还可以在低内存条件下处理卸载其nib文件(如适用)。

有关如何使用UIViewController类及其配置方式的更多信息,请参阅“用于iOS的View Controller编程指南”。

文档和窗口控制器加载相关的Nib文件

在AppKit框架中,NSDocument类与默认窗口控制器一起加载包含文档窗口的nib文件。 NSDocument的windowNibName方法是一种方便的方法,您可以使用它来指定包含相应文档窗口的nib文件。创建新文档时,文档对象将您指定的nib文件名传递给默认的窗口控制器对象,该对象加载并管理nib文件的内容。如果您使用Xcode提供的标准模板,您唯一需要做的是将文档窗口的内容添加到nib文件。

NSWindowController类还提供自动支持加载nib文件。如果以编程方式创建自定义窗口控件,则可以选择使用NSWindow对象或nib文件的名称初始化它们。如果选择后一个选项,NSWindowController类会在客户端首次尝试访问窗口时自动加载指定的nib文件。之后,窗口控制器将窗口保持在内存中;即使窗口的“关闭时释放”属性被设置,它也不会从nib文件重新加载它。

重要:当使用NSWindowController或NSDocument自动加载窗口时,重要的是您的nib文件配置正确。这两个类都包括一个窗口,您必须连接到您要管理的窗口。如果不将此插座连接到窗口对象,则nib文件已加载,但文档或窗口控制器不显示窗口。有关Cocoa文档体系结构的更多信息,请参阅“基于文档的应用程序编程指南”。

以编程方式加载Nib文件

OS X和iOS都提供了将nib文件加载到应用程序中的便利方法。 AppKit和UIKit框架都在NSBundle类上定义了支持加载nib文件的附加方法。此外,AppKit框架还提供了NSNib类,它提供与NSBundle类似的nib加载行为,但提供了在特定情况下可能有用的一些其他优点。

在计划应用程序时,请确保手动加载的任何nib文件都以简化加载过程的方式进行配置。为文件所有者选择一个适当的对象并保持你的nib文件很小可以大大提高它们的易用性和内存效率。有关配置nib文件的更多提示,请参阅Nib文件设计指南。

使用NSBundle加载Nib文件

AppKit和UIKit框架在NSBundle类(使用Objective-C类别)上定义了其他方法来支持加载nib文件资源。两种平台之间使用方法的语义与方法的语法不同。在AppKit中,通常有更多的选项可以访问bundle,因此还有更多的方法可以从这些bundle加载nib文件。在UIKit中,应用程序只能从主包装载nib文件,因此需要较少的选项。两种平台上可用的方法如下:

AppKit
loadNibNamed:owner:class方法
loadNibFile:externalNameTable:withZone:class方法
loadNibFile:externalNameTable:withZone:instance方法
UIKit
loadNibNamed:owner:options:instance方法

每当加载nib文件时,都应该始终指定一个对象作为该nib文件的文件所有者。文件所有者的作用是重要的。它是运行代码和即将在内存中创建的新对象之间的主界面。所有的nib加载方法提供了一种方法来直接指定文件的所有者,或者作为选项字典中的参数。

AppKit和UIKit框架处理nib加载的方式之间的语义差异之一就是顶层的nib对象返回到应用程序的方式。在AppKit框架中,您必须使用loadNibFile:externalNameTable:withZone:methods显式请求它们。在UIKit中,loadNibNamed:owner:options:方法直接返回这些对象的数组。在这两种情况下避免担心顶层对象的最简单的方法是将它们存储在文件所有者对象的插座中(请参阅从Nib文件管理对象的生命周期)。

清单1-1显示了一个简单的例子,说明如何使用基于AppKit的应用程序中的NSBundle类加载nib文件。 loadNibNamed:owner:方法返回后,可以开始使用任何引用nib文件对象的插座。换句话说,整个nib加载过程发生在该单个调用的限制内。 AppKit框架中的nib加载方法返回一个布尔值,以指示加载操作是否成功。

清单1-1从当前bundle加载nib文件

- (BOOL)loadMyNibFile
{
    // The myNib file must be in the bundle that defines self's class.
    if (![NSBundle loadNibNamed:@"myNib" owner:self])
    {
        NSLog(@"Warning! Could not load myNib file.\n");
        return NO;
    }
    return YES;
}

清单1-2显示了如何在基于UIKit的应用程序中加载nib文件的示例。 在这种情况下,该方法将检查返回的数组以查看nib对象是否已成功加载。 (每个nib文件应至少有一个表示nib文件内容的顶级对象。)此示例显示了当文件所有者对象不包含占位符对象时的简单情况。 有关如何指定其他占位符对象的示例,请参阅在加载时替换代理对象。

清单1-2在iPhone应用程序中加载nib

- (BOOL)loadMyNibFile
{
    NSArray*    topLevelObjs = nil;

    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"myNib" owner:self options:nil];
    if (topLevelObjs == nil)
    {
        NSLog(@"Error! Could not load myNib file.\n");
        return NO;
    }
    return YES;
}

注意:如果您正在开发适用于iOS的通用应用程序,则可以使用特定于设备的命名约定自动为底层设备加载正确的nib文件。有关如何命名nib文件的更多信息,请参阅iOS支持特定于设备的资源。

获取Nib文件的顶级对象

获取nib文件的顶级对象的最简单方法是在File的Owner对象中定义插件以及用于访问这些对象的setter方法(或更好的属性)。此方法可确保顶层对象由对象保留,并始终对其进行引用。

清单1-3显示了使用插座保留nib文件唯一顶级对象的简化Cocoa类的接口和实现。在这种情况下,nib文件中唯一的顶级对象是NSWindow对象。因为Cocoa中的顶级对象的初始保留计数为1,因此会包含一个额外的释放消息。这是很好的,因为在发出调用的时候,该属性已被保留在窗口中。您不会希望以这种方式在iPhone应用程序中释放顶级对象。

清单1-3使用出口来获取顶级对象

// Class interface.
@interface MyController : NSObject
- (void)loadMyWindow;
@end

// Private class extension.
@interface MyController ()
@property (strong) IBOutlet NSWindow *window;
@end


// Class implementation
@implementation MyController

- (void)loadMyWindow {
    [NSBundle loadNibNamed:@"myNib" owner:self];

    // The window starts off with a retain count of 1
    // and is then retained by the property, so add an extra release.
    NSWindow *window = self.window;
    CFRelease(__bridge window);
}
@end

如果您不想使用出口来存储对nib文件的顶级对象的引用,则必须在代码中手动检索这些对象。获取顶级对象的技术因目标平台而异。在OS X中,您必须明确要求对象,而在iOS中,它们将自动返回给您。

清单1-4显示了在OS X中获取nib文件的顶级对象的过程。此方法将可变数组放入nameTable字典中,并将其与NSNibTopLevelObjects关键字相关联。 nib加载代码查找此数组对象,如果存在,将顶级对象放在其中。因为每个对象在添加到数组之前以保留计数为1开始,所以简单地释放数组也不足以释放数组中的对象。因此,此方法向每个对象发送发布消息,以确保数组是唯一持有对它们的引用的实体。

清单1-4在运行时从nib文件获取顶级对象

- (NSArray*)loadMyNibFile
{
    NSBundle*            aBundle = [NSBundle mainBundle];
    NSMutableArray*      topLevelObjs = [NSMutableArray array];
    NSDictionary*        nameTable = [NSDictionary dictionaryWithObjectsAndKeys:
                                            self, NSNibOwner,
                                            topLevelObjs, NSNibTopLevelObjects,
                                            nil];

    if (![aBundle loadNibFile:@"myNib" externalNameTable:nameTable withZone:nil])
    {
        NSLog(@"Warning! Could not load myNib file.\n");
        return nil;
    }

    // Release the objects so that they are just owned by the array.
    [topLevelObjs makeObjectsPerformSelector:@selector(release)];
    return topLevelObjs;
}

获取iPhone应用程序中的顶级对象要简单得多,如清单1-2所示。在UIKit框架中,NSBundle的loadNibNamed:owner:options:方法自动返回一个包含顶级对象的数组。另外,在返回数组之前,对对象的保留计数进行调整,以便不需要向每个对象发送额外的释放消息。返回的数组是对象的唯一所有者。

使用UINib和NSNib加载Nib文件

在要创建nib文件内容的多个副本的情况下,UINib(iOS)和NSNib(OS X)类提供更好的性能。正常的nib加载过程涉及从磁盘读取nib文件,然后实例化其包含的对象。然而,使用UINib和NSNib类,从磁盘读取nib文件一次,并将内容存储在内存中。因为它们在内存中,创建连续的对象集需要更少的时间,因为它不需要访问磁盘。

使用UINib和NSNib类始终是一个两步的过程。首先,您创建一个类的实例,并使用nib文件的位置信息进行初始化。其次,您将实例化nib文件的内容以将对象加载到内存中。每次实例化nib文件时,都需要指定一个不同的File的Owner对象并接收一组新的顶级对象。

清单1-5显示了使用OS X中的NSNib类加载nib文件的内容的一种方法。由instantiateNibWithOwner返回给您的数组:topLevelObjects:方法已经自动释放。如果您打算使用该阵列任何一段时间,您应该复制一份。

清单1-5使用NSNib加载nib文件

- (NSArray*)loadMyNibFile
{
    NSNib*      aNib = [[NSNib alloc] initWithNibNamed:@"MyPanel" bundle:nil];
    NSArray*    topLevelObjs = nil;

    if (![aNib instantiateNibWithOwner:self topLevelObjects:&topLevelObjs])
    {
        NSLog(@"Warning! Could not load nib file.\n");
        return nil;
    }
    // Release the raw nib data.
    [aNib release];

    // Release the top-level objects so that they are just owned by the array.
    [topLevelObjs makeObjectsPerformSelector:@selector(release)];

    // Do not autorelease topLevelObjs.
    return topLevelObjs;
}

在加载时替换代理对象

在iOS中,可以创建除文件所有者之外的包括占位符对象的nib文件。代理对象表示在nib文件外部创建但与nib文件内容有某种连接的对象。代理通常用于支持iPhone应用程序中的导航控制器。当使用导航控制器时,您通常将文件的所有者对象连接到某些常见对象(如应用程序委托)。因此,代理对象因此表示导航控制器对象层次结构中已经加载到内存中的部分,因为它们是以编程方式创建的,也可以从不同的nib文件加载。

注意:OS X nib文件不支持自定义占位符对象(文件所有者除外)。
您添加到nib文件的每个占位符对象必须具有唯一的名称。要为对象分配名称,请选择Xcode中的对象并打开检查器窗口。检查器的“属性”窗格包含一个“名称”字段,用于指定占位符对象的名称。您分配的名称应描述对象的行为或类型,但实际上它可以是任何您想要的。

当您准备加载包含占位符对象的nib文件时,必须在调用loadNibNamed:owner:options:method时指定任何代理的替换对象。此方法的options参数接受附加信息的字典。您可以使用此字典传递有关占位符对象的信息。字典必须包含UINibExternalObjects键,其值是另一个包含每个占位符替换的名称和对象的字典。

清单1-6显示了一个applicationDidFinishLaunching:方法的示例版本,用于手动加载应用程序的主nib文件。因为应用程序的委托对象是由UIApplicationMain函数创建的,所以该方法在主nib文件中使用占位符(名称为“AppDelegate”)来表示该对象。代理字典存储占位符对象信息,并且选项字典包含该字典。

清单1-6替换nib文件中的占位符对象

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    NSArray*    topLevelObjs = nil;
    NSDictionary*    proxies = [NSDictionary dictionaryWithObject:self forKey:@"AppDelegate"];
    NSDictionary*    options = [NSDictionary dictionaryWithObject:proxies forKey:UINibExternalObjects];

    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"Main" owner:self options:options];
    if ([topLevelObjs count] == 0)
    {
        NSLog(@"Warning! Could not load myNib file.\n");
        return;
    }

    // Show window
    [window makeKeyAndVisible];
}

有关loadNibNamed选项字典的更多信息:owner:options:方法,请参阅NSBundle UIKit添加参考。

访问Nib文件的内容

成功加载nib文件后,其内容就可以立即使用。如果您在文件所有者中配置了插座来指向nib文件对象,那么现在可以使用这些插座。如果您没有使用任何插座配置文件的所有者,则应确保以某种方式获取对顶级对象的引用,以便稍后释放它们。

因为插件在加载nib文件时填充实际对象,所以随后可以像您以编程方式创建的任何其他对象一样使用插座。例如,如果您有一个指向窗口的插槽,则可以将窗口发送一个makeKeyAndOrderFront:消息,以在用户屏幕上显示该消息。完成使用nib文件中的对象后,您必须像任何其他对象一样释放它们。

重要提示:完成这些对象后,您将负责释放加载的任何nib文件的顶级对象。不这样做是许多应用程序中内存泄漏的原因。释放顶级对象后,将nib文件中指向对象的任何出口都清除为零,这是一个好主意。您应该清除与所有nib文件对象相关联的出口,而不仅仅是顶级对象。

连接Nib文件中的菜单项

OS X应用程序菜单栏中的项目通常需要与许多不同的对象进行交互,包括应用程序的文档和窗口。问题是许多这些对象不能(或不应该)直接从主nib文件访问。文件的主要nib文件的所有者始终设置为NSApplication类的一个实例。虽然您可能能够在主要nib文件中实例化一些自定义对象,但这样做是不切实际或不必要的。在文档对象的情况下,直接连接到特定文档对象是不可能的,因为文档对象的数量可以动态地更改,甚至可以为零。

大多数菜单项将动作消息发送到以下之一:

一个总是处理命令的固定对象
动态对象,如文档或窗口
消息传递固定对象是一个相对简单的过程,通常最好通过应用程序委托来处理。应用程序委托对象在运行应用程序时协助NSApplication对象,并且是主要nib文件中正确属于的少数对象之一。如果菜单项是指应用程序级命令,则可以直接在应用程序委托中实现该命令,或者只需让代理将消息转发到应用程序其他位置的相应对象。

如果您有一个菜单项作用于最前面的窗口的内容,则需要将菜单项链接到第一个响应者占位符对象。如果与菜单项相关联的操作方法特定于您的一个对象(而不是由Cocoa定义),则必须在创建连接之前将该操作添加到第一个响应程序。

创建连接后,您需要在自定义类中实现操作方法。该对象还应实现validateMenuItem:方法,以在适当的时间启用菜单项。有关响应者链如何处理命令的更多信息,请参阅cocoa事件处理指南。

推荐阅读更多精彩内容

  • 译者注:本文是对 Apple 官方文档的翻译,原文地址为:https://developer.apple.com/...
    ampire_dan阅读 3,293评论 0 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 71,220评论 12 116
  • 介绍 捆绑是macOS和iOS中用于封装代码和资源的基础技术。软件包通过为所需资源提供已知位置来简化开发人员体验,...
    nicedayCoco阅读 324评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 125,117评论 16 537
  • 再过几天,就19岁了,我想把我的过去描述给大家,但故事太多,事情太杂。但既然决定在简书上写故事,我便不会半途而废。...
    筱煜_21e4阅读 17评论 0 0