關于資源
適用于計算機程序的資源是與程序可執行代碼相關的數據文件。資源可以通過將代碼之外的復雜數據集或圖形內容創建到更合適的工具中來簡化您必須編寫的代碼。例如,使用代碼逐個像素地創建圖像,在圖像編輯器中創建它們的效率(和實用性)要高得多。為了利用資源,所有的代碼都必須在運行時加載它并使用它。
除了簡化代碼之外,資源也是所有應用程序的國際化流程的一個親密的部分。您可以將應用程序中的字符串和其他用戶可見內容硬編碼,而不是將內容放在外部資源文件中。本地化您的應用程序然后成為為每種支持的語言創建每個資源文件的新版本的簡單過程。在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事件處理指南。