macOS 和 iOS 中 Nib 文件實現原理以及最佳實踐

譯者注:本文是對 Apple 官方文檔的翻譯,原文地址為:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html

iOS 和 macOS 中的 xib 文件以及 storyboard 面板最終會被 Xcode 打包系統處理生成相應的 nib 文件并放置到 ipa 包中。所以可以說本文是對這兩種文件底層原理的解析。同時該文檔也是實踐指南。

<a name="table-content"></a>目錄

<a name="Nib-Files"></a> Nib 文件

在 OS X 和 iOS 創建應用程序中起著重要的作用。使用 nib 文件,您可以使用 Xcode 以圖形方式創建和操作用戶界面(而不是以編程方式)。因為您可以立即查看更改的結果,您可以快速嘗試不同的布局和配置。您也可以稍后更改用戶界面的許多方面,而無需重寫任何代碼。

對于使用 AppKit 或 UIKit 框架構建的應用程序,nib 文件具有重要意義。這兩個框架都支持使用 nib 文件來布局窗口、視圖和控件,并將這些項目與應用程序的事件處理代碼集成在一起。Xcode 與這些框架配合使用,可幫助您將用戶界面的控件連接到項目中,設置為您自定義的對象。該集成顯著減少了加載 nib 文件后所需的設置量,并且在以后可以輕松更改代碼和用戶界面之間的關系。

注意:雖然可以在不使用 nib 文件的情況下創建 Objective-C 應用程序,但這樣做非常罕見,不推薦。 根據您的應用程序,避免 nib 文件可能需要您編程大量的框架行為才能實現與使用 nib 文件相同的結果。

↑目錄


<a name="Anatomy-of-a-Nib-File"></a> 解剖 Nib 文件

一個 nib 文件描述了應用程序的用戶界面的視覺元素,包括窗口,視圖,控件等等。 它還可以描述非可視元素,例如應用程序中管理窗口和視圖的對象。 更重要的是,在 Xcode 中配置的對象最后會一一映射到 nib 文件描述的對象。 在運行時,這些描述用于在應用程序中重新創建對象及其配置。 當您在運行時加載 nib 文件時,您將獲得 Xcode 文檔中對象的精確副本。 nib 加載代碼實例化對象,配置它們,并重新建立您在 nib 文件中創建的任何對象間的連接。

以下部分描述了如何組織與 AppKit 和 UIKit 框架一起使用的 nib 文件,在其中找到的對象類型以及如何有效地使用這些對象。

<a name="About-Your-Interface-Objects"></a> 關于您的界面對象

界面對象是添加到 nib 文件中來實現用戶界面的對象。當在運行時加載一個 nib 時,界面對象是由 nib 加載代碼實際實例化的對象。大多數新的 nib 文件默認情況下至少有一個界面對象,通常是窗口或菜單資源,并且可以在界面設計中添加更多的界面對象到 nib 文件。這是 nib 文件中最常見的對象類型,通常也是您為什么要使用 nib 文件的原因。

除了表示視覺對象,如窗口,視圖,控件和菜單之外,界面對象還可以表示非視覺對象。在幾乎所有情況下,添加到 nib 文件的非可視對象都是您的應用程序用于管理可視對象的額外控制器對象。雖然您可以在應用程序中創建這些對象,但將其添加到 nib 文件并將其配置在那里通常更為方便。 Xcode 提供了一個通用對象,特別地,您可以在將控制器和其他非可視對象添加到 nib 文件時使用。它還提供了通常用于管理 Cocoa 綁定的控制器對象。

<a name="About-the-File’s-Owner"></a> 關于文件的所有者

nib 文件中最重要的對象是 File's Owner 對象。 與界面對象不同,File's Owner 對象是一個占位符對象,在加載 nib 文件時不會創建。 相反,您可以在代碼中創建此對象,并將其傳遞給 nib 加載代碼。 這個對象如此重要的原因是它是應用程序代碼和 nib 文件內容之間的主要鏈接。 更具體地說,它是負責 nib 文件內容的控制器對象。

在 Xcode 中,您可以在文件的所有者和 nib 文件中的其他接口對象之間創建連接。 加載 nib 文件時,nib 加載代碼將使用您指定的替換對象重新創建這些連接。 這允許您的對象引用 nib 文件中的對象并自動從界面對象接收消息。

<a name="About-the-First-Responder"></a> 關于第一響應者

在 nib 文件中,First Responder 是一個占位符對象,表示應用程序動態確定的響應鏈中的第一個對象。因為應用程序的響應鏈無法在設計時確定,所以第一響應者占位符充當需要針對應用程序的響應鏈的任何操作消息的備用目標。菜單項通常充當第一響應者占位符。例如,“窗口”菜單中的“最小化”菜單項隱藏應用程序中的最前面的窗口,而不僅僅是特定的窗口,“復制”菜單項應該復制當前選擇,而不僅僅是單個控件或視圖的選擇。應用程序中的其他對象也可以成為第一響應者。

當您將 nib 文件加載到內存中時,您無需管理或替換第一響應者占位符對象。 AppKit 和 UIKit 框架根據應用程序的當前配置自動設置和維護第一響應者。

有關響應鏈的更多信息以及如何用于在基于 AppKit 的應用程序中分派事件,請參閱 Event Architecture。有關 iPhone 應用程序中的響應鏈和處理操作的信息,請參閱 Event Handling Guide for UIKit Apps

<a name="About-the-Top-Level-Objects"></a> 關于頂級對象

當您的程序加載 nib 文件時,Cocoa 會重新創建 Xcode 中創建的整個對象圖。該對象圖包括在 nib 文件中找到的所有窗口、視圖、控件、單元格、菜單和自定義對象。頂級對象是不具有父對象的這些對象的子集。頂級對象通常僅包含添加到 nib 文件中的窗口、菜單欄和自定義控制器對象。 (諸如 File's Owner,第一響應者和應用程序的對象是占位符對象,并不被視為頂級對象。)

通常,您使用 File's Owner 對象中的 outlet 來存儲對 nib 文件的頂級對象的引用。如果不使用 outlet,則可以直接從 nib 加載例程中檢索頂層對象。您應該始終保持一個指向這些對象的指針,因為您的應用程序負責使用完之后釋放它們。有關加載時的 nib 對象行為的更多信息,請參閱 管理 Nib 文件中對象的生命周期

<a name="About-Image-and-Sound-Resources"></a> 關于圖像和聲音資源

在 Xcode 中,您可以從 nib 文件的內容中引用外部圖像和聲音資源。一些控件和視圖能夠顯示圖像或播放聲音作為默認配置的一部分。 Xcode 庫提供對 Xcode 項目中的圖像和聲音資源的訪問支持,以便您可以將 nib 文件鏈接到這些資源。 nib 文件不直接存儲這些資源。相反,它存儲資源文件的名稱,以便 nib 加載代碼稍后可以找到。

加載包含圖像或聲音資源引用的 nib 文件時,nib 加載代碼將實際的圖像或聲音文件讀入內存并緩存。在 OS X 中,圖像和聲音資源存儲在命名的緩存中,以便以后可以在需要時訪問它們。在 iOS 中,只有圖像資源存儲在命名的緩存中。要訪問圖像,請使用 NSImage 或 UIImage 的 imageNamed: 方法,具體取決于您的平臺。要在OS X 中訪問緩存的聲音,請使用 NSSound 的 soundNamed: 方法。

↑目錄


<a name="Nib-File-Design-Guidelines"></a> Nib 文件設計指南

創建 nib 文件時,請仔細考慮如何打算使用該文件中的對象。一個非常簡單的應用程序可能將所有用戶界面組件存儲在單個 nib 文件中,但對于大多數應用程序,最好將組件分布在多個 nib 文件中。創建較小的 nib 文件可讓您立即加載您需要的界面部分。它們還可以更容易地調試您可能遇到的任何問題,因為需要檢查的地方較少。

創建 nib 文件時,請牢記以下準則:

  • 用懶加載設計你的 nib 文件。計劃加載僅包含您需要的對象的 nib 文件。
  • 在 OS X 應用程序的主要 nib 文件中,請考慮僅將應用程序菜單欄和可選應用程序委托對象存儲在 nib 文件中。避免在應用程序啟動后包括任何不會使用的窗口或用戶界面元素。相反,將這些資源放在單獨的 nib 文件中,并在啟動后根據需要加載它們。
  • 將重復的用戶界面組件(如文檔窗口)存儲在單獨的 nib 文件中。
  • 對于僅偶爾使用的窗口或菜單,將其存儲在單獨的 nib 文件中。通過將其存儲在單獨的 nib 文件中,只有在實際使用時才將資源加載到內存中。
  • 使 File's Owener 對象成為 nib 文件外的任何單一聯系人。請參閱 訪問 Nib 文件的內容

↑目錄


<a name="The-Nib-Object-Life-Cycle"></a> Nib 對象生命周期

當 nib 文件加載到內存中時,nib 加載代碼需要幾個步驟來確保 nib 文件中的對象被正確創建和初始化。 了解這些步驟可以幫助您編寫更好的控制器代碼來管理用戶界面。

<a name="The-Object-Loading-Process"></a> 對象加載過程

當您使用 NSNib 或 NSBundle 的方法加載和實例化 nib 文件中的對象時,底層的
nib 加載代碼執行以下操作:

  1. 它將 nib 文件和任何引用的資源文件的內容加載到內存中:

    • 整個 nib 對象圖的原始數據被加載到內存中,但沒有解檔。
    • nib 文件相關聯的任何自定義圖像資源都將被加載并添加到 Cocoa 圖像緩存中。請參閱關于圖像和聲音資源
    • nib 文件相關聯的任何自定義聲音資源都將被加載并添加到 Cocoa 聲音緩存;請參閱 關于圖像和聲音資源
  2. 它解檔 nib 對象圖數據并實例化對象。如何初始化每個新對象取決于對象的類型及其在歸檔中的編碼方式。 nib 加載代碼使用以下規則(按順序)來確定要使用的初始化方法。

    • 默認情況下,對象會收到一個 initWithCoder: 消息。

      • 在 OS X 中,標準對象列表包括系統提供的視圖,單元格,菜單和視圖控制器,并且在默認的 Xcode 庫中可用。它還包括使用自定義插件添加到庫的任何第三方對象。即使您更改了這樣一個對象的類,Xcode 將標準對象編碼到 nib 文件中,然后在對象被解檔時通知歸檔器使用改自定義類。

      • 在 iOS 中,使用 initWithCoder: 方法初始化符合 NSCoding 協議的任何對象。這包括 UIView 和 UIViewController 的所有子類,無論它們是默認的 Xcode 庫或定義的自定義類的一部分。

    • OS X 中自定義視圖會收到一條 initWithFrame: 消息。

      • 自定義視圖是 NSView 的子類,Xcode 沒有可用的實現。通常,這些是您在應用程序中定義并用于提供自定義可視內容的視圖。自定義視圖不包括作為默認庫或集成第三方插件的一部分的標準系統視圖(如 NSSlider)。

      • 當它遇到自定義視圖時,Xcode 將一個特殊的 NSCustomView 對象編碼到你的 nib 文件中。自定義視圖對象包括構建您指定的真實視圖子類所需的信息。在加載時,NSCustomView 對象將一個 allocinitWithFrame: 消息發送到真實的視圖類,然后交換自己生成的視圖對象。凈效果是真正的視圖對象處理在 nib 加載過程中的后續交互。

      • iOS 中的自定義視圖不會使用 initWithFrame:方法進行初始化。

    • 除了上述步驟中描述的以外的自定義對象接收 init 初始消息。

  3. 重新建立 nib 文件中對象之間的所有連接(action、outlet 和綁定)。 這包括與 File's Owner 和其他占位符對象的連接。 建立連接的方法因平臺而異:

    • Outlet 連接
      • 在 OS X 中,nib 加載代碼首先嘗試使用對象自己的方法重新連接 outlet。對于每個 outlet ,Cocoa 查找一個形式為 setOutletName 的方法,如果存在這樣的方法,則調用它。如果找不到這樣的方法,Cocoa 會在對象中搜索具有相應 outlet 名稱的實例變量,并嘗試直接設置值。如果找不到實例變量,則不會創建任何連接。
        設置 outlet 還會為任何注冊的觀察者生成鍵值觀察(KVO)通知。這些通知可能在所有對象間連接重新建立之前發生,并且在調用任何對象的 awakeFromNib 方法之前肯定會發生這些通知。
      • 在 iOS 中,nib 加載代碼使用 setValue:forKey:方法重新連接每個 outlet。該方法同樣尋找適當的訪問器方法,并且在失敗時使用其他方式。有關此方法如何設置值的更多信息,請參閱其在 NSKeyValueCoding Protocol Reference 的描述。
      • 在 iOS 中設置 outlet 還會為任何注冊的觀察者生成 KVO 通知。這些通知可能在所有對象間連接重新建立之前發生,并且在調用任何對象的 awakeFromNib 方法之前肯定會發生這些通知。
    • 動作連接
      • 在 OS X 中,nib 加載代碼使用源對象的 setTarget:setAction:方法來建立與目標對象的連接。如果目標對象沒有響應 action 方法,則不會創建任何連接。如果目標對象為空,則該動作由響應鏈處理。
      • 在 iOS 中,nib 加載代碼使用 UIControl 對象的 addTarget:action:forControlEvents: 方法來配置操作。如果目標為空,則動作由響應鏈處理。
    • 綁定
      • 在 OS X 中,Cocoa 使用源對象的 bind:toObject:withKeyPath:options: 方法來創建它與其目標對象之間的連接。
      • iOS 中不支持綁定。
  4. 將一個 awakeFromNib 消息發送到 nib 文件中定義匹配選擇器的相應對象:

    • 在 OS X 中,該消息被發送到定義該方法的任何界面對象。它也被發送到 File's Owner 和定義的任何占位符對象。
    • 在 iOS 中,此消息僅發送到由 nib 加載代碼實例化的界面對象。它不發送到 File's Owner、第一響應者或任何其他占位符對象。
  5. 顯示在 nib 文件中啟用了“Visible at launch time”屬性的任何窗口。

nib 加載代碼調用對象的 awakeFromNib 方法的順序是不能保證的。在 OS X 中,Cocoa嘗試最后調用 File‘Owner 的 awakeFromNib 方法,但不能保證該行為。如果您需要在加載時進一步配置 nib 文件中的對象,則最適合的時間是在您的 nib 加載調用返回后。這時,所有的對象都被創建,初始化并準備好使用。

↑目錄


<a name="Managing-the-Lifetimes-of-Objects-from-Nib-Files"></a> 管理 Nib 文件中對象的生命周期

每次您要求 NSBundle 或 NSNib 類加載 nib 文件時,底層代碼會創建該文件中的對象的新副本并將其返回給您。(nib 加載代碼不會從以前的加載嘗試中回收 nib 文件對象。)您需要確保在必要時保持新的對象圖,并在完成之后將其取消。通常需要對頂級對象的強引用以確保它們不被釋放。您不需要強引用對象圖中較低的對象,因為它們由父母結點擁有,您應該盡可能減少引用循環的風險。

從實際的角度來看,iOS 和 OS X 的 outlet 應該被定義為聲明的屬性。 Outlets 通常應該是 weak 的,除了 nib 文件對應的 File's Owner 、和 nib 文件擁有的頂級對象(或 iOS 的故事板場景中)應該是強的。因此,您創建的 outlet 通常應該是 weak 的,因為:

  • 例如,您創建的從視圖控制器 view 或窗口控制器窗口的子視圖的 outlet 是不暗示所有權的對象之間的任意引用。
  • 強大的 outlet 經常由框架類指定(例如,UIViewController 的 view outlet 或 NSWindowController 的窗口 outlet)。
@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;

注意:在 OS X 中,并非所有類都支持弱引用 - 請參閱 Transitioning to ARC Release Notes。 在不能指定的情況下,您應該使用 assign

@property (assign) IBOutlet NSTextView *textView;

Outlet 通常被認為是定義類別的私有; 除非有理由公開揭露屬性,否則隱藏屬性聲明類擴展名。 例如:

// MyClass.h
 
@interface MyClass : MySuperclass
@end
 
// MyClass.m
 
@interface MyClass ()
@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;
@end

這些模式擴展到從容器視圖到其子視圖的引用,您必須考慮對象圖的內部一致性。 例如,在表視圖單元格的情況下,特定子視圖的 outlet 通常是 weak。 如果表視圖包含圖像視圖和文本視圖,那么這些視圖仍然有效,只要它們是表視圖單元本身的子視圖。

當 outlet 被認為擁有參考對象時,outlet 應變為 strong:

  • 如前所述,通常情況下,top level 的對象在 nib 文件中經常被認為是由 File's Owner 擁有的。
  • 在某些情況下,您可能需要一個來自 nib 文件的對象存在其原始容器之外。 例如,您可能有一個視圖的 outlet,可以臨時從其初始視圖層次結構中刪除,因此必須獨立進行維護。

您希望被子類化的類(特別是抽象類)公開暴露 outlet,以便它們可以被子類(例如UIViewController 的視圖 outlet)適當地使用。 如果使用者需要訪問屬性,則 outlet 也可能會暴露出來;.例如,表視圖單元格可能會暴露子視圖。 在后一種情況下,可以適合公開一個以私有定義可讀的只讀公共 outlet ,例如:

// MyClass.h
 
@interface MyClass : UITableViewCell
@property (weak, readonly) MyType *outletName;
@end
 
// MyClass.m
 
@interface MyClass ()
@property (weak, readwrite) IBOutlet MyType *outletName;
@end

<a name="Top-level-Objects-in-OS-X-May-Need-Special-Handling"></a> OS X 中的頂級對象可能需要特殊處理

由于歷史原因,在 OS X 中,將創建一個 nib 文件的頂級對象,并附加一個引用計數。應用開發框架提供了幾個功能,可幫助確保正確釋放 nib 對象:

  • NSWindow 對象(包括面板)有一個 isReleasedWhenClosed 屬性,如果設置為YES,則會在窗口關閉時指示窗口釋放自身(以及其視圖層次結構中的所有相關對象)。在 nib 文件中,您可以通過 Xcode inspector 的“Attribute”窗格中的“Release when closed ”復選框來設置此選項。

  • 如果 nib 文件的 File's Owner 是 NSWindowController 對象(在基于文檔的應用程序中的文檔 nib 中的默認值,請記住 NSDocument 管理 NSWindowController 的一個實例)或 NSViewController 對象,它會自動處理其管理的窗口。

如果 File's Owner 不是 NSWindowController 或 NSViewController 的實例,那么您需要自己遞減頂級對象的引用計數。您必須將頂級對象的引用轉換為 Core Foundation 類型并使用 CFRelease。 (如果不希望有所有頂級對象的 outlet ,可以使用 NSNib 類的 instantiateNibWithOwner:topLevelObjects:方法來獲取一個 nib文件頂層對象的數組。)

↑目錄


<a name="Action-Methods"></a> Action Methods

一般來說,action method(參見 OS X 中的 Target-Action 或 iOS 中的 Target-Action)是通常由 nib 文件中另一個對象調用的方法。 Action method 使用類型限定符 IBAction(void類型的替代),將聲明的方法標記為一個 action method,以便Xcode 知道它。

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

您可以選擇將 action method 視為您的類私有的,因此不會在 public @interface 中聲明它們。 (因為 Xcode 解析實現文件,所以不需要在頭文件中聲明它們。)

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

您通常不應以編程方式調用 action method。 如果您的類需要執行與 action method相關聯的工作,那么您應該將實現應用到另一種由 action mehtod 調用的方法中。

// 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

↑目錄


<a name="Built-In-Support-For-Nib-Files"></a> Nib 文件的內置支持

AppKit 和 UIKit 框架都提供了一定量的自動化行為來加載和管理應用程序中的 nib 文件。 這兩個框架都提供了用于加載應用程序主要 nib 文件的基礎設施。 此外,AppKit 框架提供了通過 NSDocument 和 NSWindowController 類加載其他 nib 文件的支持。 以下部分介紹了 nib 文件的內置支持,如何利用它們,以及在自己的應用程序中修改該支持的方法。

<a name="The-Application-Loads-the-Main-Nib-File"></a> 應用程序加載主 Nib 文件

大多數 Xcode 項目模板都已預先為應用程序配置了一個主要的 nib 文件。所有您需要做的是修改默認 nib 文件并構建您的應用程序。在啟動時,應用程序的默認配置數據告訴應用程序對象找到該 nib 文件,以便加載它。在基于 AppKit 和 UIKit 的應用程序中,此配置數據位于應用程序的 Info.plist 文件中。首次加載應用程序時,應用程序啟動代碼默認會在 Info.plist 文件中查找 NSMainNibFile 鍵。如果找到它,它會在應用程序包中查找一個 nib 文件,其名稱(帶或不帶文件擴展名)與該鍵的值匹配,然后加載它。

<a name="Each-View-Controller-Manages-its-Own-Nib-File"></a> 每個視圖控制器管理其自己的 Nib 文件

UIViewController(iOS)和NSViewController(OS X)類支持自動加載其關聯的 nib文件。如果在創建視圖控制器時指定 nib 文件,則當您嘗試訪問視圖控制器的視圖時,該 nib 文件會自動加載。視圖控制器和 nib 文件對象之間的任何連接都將自動創建,在 iOS 中,當視圖最終加載并顯示在屏幕上時,UIViewController 對象還會收到其他通知。為了更好地管理內存,UIViewController 類還可以在低內存條件下處理卸載其 nib 文件(如適用)。

有關如何使用 UIViewController 類及其配置方式的更多信息,請參閱 View Controller Programming Guide for iOS

<a name="Document-and-Window-Controllers-Load-Their-Associated-Nib-File"></a> 文檔和窗口控制器加載相關的 Nib 文件

在 AppKit 框架中,NSDocument 類與默認窗口控制器一起加載包含文檔窗口的 nib 文件。 NSDocument的 windowNibName 方法是一種方便的方法,您可以使用它來指定包含相應文檔窗口的 nib 文件。創建新文檔時,文檔對象將您指定的 nib 文件名傳遞給默認的窗口控制器對象,該對象加載并管理 nib 文件的內容。如果您使用Xcode 提供的標準模板,您唯一需要做的是將文檔窗口的內容添加到 nib 文件中。

NSWindowController 類還提供自動支持加載 nib 文件。如果以編程方式創建自定義窗口控件,則可以選擇使用 NSWindow 對象或 nib 文件的名稱初始化它們。如果選擇后一個選項,NSWindowController 類會在客戶端首次嘗試訪問窗口時自動加載指定的 nib 文件。之后,窗口控制器將窗口保持在內存中。即使窗口的“Release when closed”屬性被設置,它也不會從 nib 文件重新加載它。

重要:當使用 NSWindowController 或 NSDocument 自動加載窗口時,重要的是您的 nib 文件配置正確。 這兩個類都包括一個窗口 outlet,您必須連接到您要管理的窗口。 如果不將此 outlet 連接到窗口對象,則 nib 文件已加載,但文檔或窗口控制器不顯示窗口。 有關 Cocoa 文檔體系結構的更多信息,請參閱 Document-Based App Programming Guide for Mac

↑目錄


<a name="Loading-Nib-Files-Programmatically"></a> 以編程方式加載 Nib 文件

OS X 和 iOS 都提供了將 nib 文件加載到應用程序中的便利方法。 AppKit 和 UIKit 框架都在 NSBundle 類上定義了支持加載 nib 文件的附加方法。 此外,AppKit 框架還提供了 NSNib 類,它提供與 NSBundle 類似的 nib 加載行為,但提供了在特定情況下可能有用的一些其他優點。

在設計應用程序時,請確保手動加載的任何 nib 文件都以簡化加載過程的方式進行配置。 為 File's Owner 選擇一個適當的對象并保持你的 nib 文件很小可以大大提高它們的易用性和內存效率。 有關配置 nib 文件的更多提示,請參閱 Nib 文件設計指南

<a name="Loading-Nib-Files-Using-NSBundle"></a> 使用NSBundle加載Nib文件

AppKit 和 UIKit 框架在 NSBundle 類(使用 Objective-C 類別)上定義了其他方法來支持加載 nib 文件資源。 兩平臺之間使用方法的語義與方法的語法不同。 在 AppKit 中,通常有更多的選項可以訪問 bundle,因此還有更多的方法可以從這些 bundle 加載 nib 文件。 在 UIKit 中,應用程序只能從 main bundle 裝載 nib 文件,因此需要較少的選項。 兩種平臺上可用的方法如下:

  • AppKit:

    • loadNibNamed:owner: 類方法
    • loadNibFile:externalNameTable:withZone: 類方法
    • loadNibFile:externalNameTable:withZone: 對象方法
  • UIKit

    • loadNibNamed:owner:options: instance方法

每當加載 nib 文件時,都應該始終指定一個對象作為該 nib 文件的 File's Owner。File's Owner 的作用很重要的。它是運行代碼和即將在內存中創建的新對象之間的主要接口。所有的 nib 加載方法提供了一種方法來直接指定 Files' Owner,或者作為選項字典中的參數。

AppKit 和 UIKit 框架處理 nib 加載的方式之間的語義差異之一就是頂層的 nib 對象返回到應用程序的方式。在 AppKit 框架中,您必須使用 loadNibFile:externalNameTable:withZone:methods 顯式請求它們。在 UIKit 中,loadNibNamed:owner:options: 方法直接返回這些對象的數組。在這兩種情況下避免擔心頂層對象的最簡單的方法是將它們存儲在 File's Owner 的 outlets 中(請參閱 管理 Nib 文件中對象的生命周期)。

清單1-1 顯示了一個簡單的例子,說明如何使用基于 AppKit 的應用程序中的 NSBundle 類加載 nib 文件。 loadNibNamed:owner:方法返回后,可以使用任何引用 nib 文件對象的 outlet。換句話說,整個 nib 加載過程發生在該單個調用的限制內。 AppKit 框架中的 nib 加載方法返回一個布爾值,以指示加載操作是否成功。

清單 1-1 Loading a nib file from the current bundle

- (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 Loading a nib in an iPhone application

- (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 Supports Device-Specific Resources

<a name="Getting-a-Nib-File’s-Top-Level-Objects"></a> 獲取 Nib 文件的頂級對象

獲取 nib 文件的頂級對象的最簡單方法是在 File's Owner 對象中定義 outlets 以及用于訪問這些對象的 setter 方法(或屬性)。 此方法可確保頂層對象由對象保留,并始終對其進行引用。

清單 1-3 顯示了使用 outlet 保留 nib 文件唯一頂級對象的簡化 Cocoa 類的接口和實現。 在這種情況下,nib 文件中唯一的頂級對象是 NSWindow對象。 因為 Cocoa 中的頂級對象的初始引用計數為1,因此會包含一個額外的釋放消息。 這很好,因為在發出調用的時候,該屬性已被保留在窗口中。 您不會希望以這種方式在 iPhone 應用程序中釋放頂級對象。

清單 1-3 Using outlets to get the top-level objects

// 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

如果您不想使用 outlet 來存儲對 nib 文件的頂級對象的引用,則必須在代碼中手動檢索這些對象。 獲取頂級對象的技術因目標平臺而異。 在 OS X 中,您必須明確要求對象,而在 iOS 中,它們將自動返回給您。

清單 1-4 顯示了在 OS X 中獲取 nib 文件的頂級對象的過程。此方法將可變數組放入 nameTable 字典中,并將其與 NSNibTopLevelObjects 關鍵字相關聯。 nib 加載代碼查找此數組對象,如果存在,將頂級對象放在其中。 因為每個對象在添加到數組之前以保留計數為1開始,所以簡單地釋放數組也不足以釋放數組中的對象。 因此,此方法向每個對象發送發送消息,以確保數組是唯一持有對它們的引用的實體。

清單 1-4 Getting the top-level objects from a nib file at runtime

- (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: 方法自動返回一個包含頂級對象的數組。另外,在返回數組之前,對對象的保留計數進行調整,以便不需要向每個對象發送額外的釋放消息。返回的數組是對象的唯一所有者。

<a name="Loading-Nib-Files-Using-UINib-and-NSNib"></a> 使用 UINib 和 NSNib 加載 Nib 文件

在要創建 nib 文件內容的多個副本的情況下,UINib(iOS)和 NSNib(OS X)類提供更好的性能。正常的 nib 加載過程涉及從磁盤讀取 nib 文件,然后實例化其包含的對象。然而,使用 UINib 和 NSNib 類,從磁盤讀取 nib 文件只要一次,并將內容存儲在內存中。因為它們在內存中,創建連續的對象集需要更少的時間,因為它不需要訪問磁盤。

使用 UINib 和 NSNib 類始終是一個兩步的過程。首先,您創建一個類的實例,并使用 nib 文件的位置信息進行初始化。其次,您將實例化 nib 文件的內容以將對象加載到內存中。每次實例化 nib 文件時,都需要指定一個不同的 File's Owner 對象并接收一組新的頂級對象。

清單 1-5 顯示了使用 OS X 中的 NSNib 類加載 nib 文件的內容的一種方法。由instantiateNibWithOwner :topLevelObjects:方法返回給您的數組已經自動釋放。如果您打算使用該數組任何一段時間,您應該復制一份。

清單 1-5 Loading a nib file using NSNib

- (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;
}

<a name="Replacing-Proxy-Objects-at-Load-Time"></a> 在加載時替換代理對象

在 iOS 中,可以創建除 File's Owner 之外的包括占位符對象的 nib 文件。 代理對象表示在 nib 文件外部創建但與 nib 文件內容有某種連接的對象。 代理通常用于支持 iPhone 應用程序中的導航控制器。 當使用導航控制器時,您通常將文件的所有者對象連接到某些常見對象(如應用程序委托)。 因此,代理對象表示導航控制器對象層次結構中已經加載到內存中的部分,因為它們是以編程方式創建的(也可以從不同的 nib 文件加載)。

注意:OS X nib 文件不支持自定義占位符對象(File's Owner 除外)。

您添加到 nib 文件的每個占位符對象必須具有唯一的名稱。要為對象分配名稱,請選擇 Xcode 中的對象并打開 insepector 窗口。insepector 的“Attributes”窗格包含一個“Name”字段,用于指定占位符對象的名稱。您分配的名稱應描述對象的行為或類型,但實際上它可以是任何您想要的。

當您準備加載包含占位符對象的 nib 文件時,必須在調用 loadNibNamed:owner:options:method 時指定任何代理的替換對象。此方法的options 參數接受附加信息的字典。您可以使用此字典傳遞有關占位符對象的信息。字典必須包含 UINibExternalObjects 鍵,其值是另一個包含每個占位符替換的名稱和對象的字典。

清單 1-6 顯示了一個 applicationDidFinishLaunching: 方法的示例版本,用于手動加載應用程序的主 nib 文件。因為應用程序的委托對象是由 UIApplicationMain 函數創建的,所以該方法在主 nib 文件中使用占位符(名稱為“AppDelegate”)來表示該對象。代理字典存儲占位符對象信息,并且選項字典包含該字典。

清單 1-6 Replacing placeholder objects in a nib file

- (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 Additions Reference

<a name="Accessing-the-Contents-of-a-Nib-File"></a> 訪問 Nib 文件的內容

成功加載 nib 文件后,其內容就可以立即使用。如果您在 File's Owner 中配置了 outlet 來指向 nib 文件中的對象,那么現在可以使用這些 outlet。如果您沒有使用任何 outlet 配置 File's Owner ,則應確保以某種方式獲取對頂級對象的引用,以便稍后釋放它們。

因為 outlets 在加載 nib 文件時填充實際對象,所以隨后可以像您以編程方式創建的任何其他對象一樣使用 outlet。例如,如果您有一個指向窗口的 outlet,則可以將窗口發送一個 makeKeyAndOrderFront: 消息,以在用戶屏幕上顯示該消息。完成使用
nib 文件中的對象后,您必須像任何其他對象一樣釋放它們。

重要提示:使用這些對象后,您將負責釋放加載的任何 nib 文件的頂級對象。不這樣做是許多應用程序中內存泄漏的原因。釋放頂級對象后,將 nib 文件中指向對象的任何 outlet 都清除為 nil (weak 屬性的不需要手動處理),這是一個好主意。您應該清除與所有 nib 文件對象相關聯的 outlet,而不僅僅是頂級對象。

↑目錄


<a name="Connecting-Menu-Items-Across-Nib-Files"></a> 連接 Nib 文件中的菜單項

OS X 應用程序菜單欄中的項目通常需要與許多不同的對象進行交互,包括應用程序的文檔和窗口。問題是許多這些對象不能(或不應該)直接從主 nib 文件訪問。主 nib 文件的 File's Owner 始終設置為 NSApplication 類的一個實例。雖然您可能能夠在主 nib 文件中實例化一些自定義對象,但這樣做是不切實際或不必要的。在文檔對象的情況下,直接連接到特定文檔對象是不可能的,因為文檔對象的數量可以動態地更改,甚至可以為 nil。

大多數菜單項將動作消息發送到以下之一:

  • 一個總是處理命令的固定對象
  • 動態對象,如文檔或窗口

消息傳遞給固定對象是一個相對簡單的過程,通常最好通過應用程序委托來處理。應用程序委托對象在運行應用程序時協助 NSApplication 對象,并且是少數幾個包括在主 nib 文件的。如果菜單項是指應用程序級命令,則可以直接在應用程序委托中實現該命令,或者只需讓代理將消息轉發到應用程序其他位置的相應對象。

如果您有一個菜單項作用于最前面的窗口的內容,則需要將菜單項鏈接到第一個響應者占位符對象。如果與菜單項相關聯的操作方法特定于您的一個對象(而不是由Cocoa 定義),則必須在創建連接之前將該操作添加到第一響應程序。

創建連接后,您需要在自定義類中實現操作方法。該對象還應實現validateMenuItem: 方法,以在適當的時間啟用菜單項。有關響應鏈如何處理命令的更多信息,請參閱 Cocoa Event Handling Guide

↑目錄


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容

  • 關于資源 適用于計算機程序的資源是與程序可執行代碼相關的數據文件。資源可以通過將代碼之外的復雜數據集或圖形內容創建...
    nicedayCoco閱讀 650評論 0 0
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,778評論 18 139
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,184評論 30 470
  • Core Animation基礎 Core Animation 利用了硬件加速和架構上的優化來實現快速渲染和實時動...
    獨木舟的木閱讀 1,545評論 0 3
  • 翻譯自“Collection View Programming Guide for iOS” 0 關于iOS集合視...
    lakerszhy閱讀 3,901評論 1 22