=== 原文地址: http://trac.clozure.com/ccl/browser/trunk/source/examples/cocoa/nib-loading/HOWTO.html 原文標題: Nib-Loading HOWTO 翻譯者: FreeBlues 2013-06-17html
===程序員
這篇教程說明如何經過對 Lisp 形式(forms)求值, 加載 nibfiles 到正在運行的 Clozure CL 副本中。你可能想經過這種方式加載 nibfiles 來爲你正在工做的一個應用程序項目來測試用戶界面元素,或使應用程序可以動態加載可選的用戶界面元素。數組
Cocoa應用程序開發有很大一部分是使用Cocoa框架來建立用戶界面元素的。雖然它是徹底有可能只是經過對框架的方法調用建立任何用戶界面元素,設計用戶界面更標準的方式是使用蘋果的 InterfaceBuilder 應用程序去建立 nibfiles 文件, 這些文件歸檔於 Objective-C 對象,實現用戶界面元素。數據結構
Interface builder 是蘋果的開發工具附帶的一個應用程序。開發工具是 Mac OS X 自帶的一個可選的安裝,在你使用這個HOWTO以前,您須要確保您的系統上安裝了蘋果的開發工具。蘋果的開發者計劃的成員能夠免費從蘋果的開發者網站下載工具,但一般沒有必要。你能夠簡單地使用 Mac OS X系統磁盤上可選的開發者工具安裝程序來安裝工具。app
使用 InterfaceBuilder,您能夠快速,輕鬆地建立窗口,對話框,文本字段,按鈕和其餘用戶界面元素。你用 InterfaceBuilder 建立的的界面元素擁有符合蘋果人機界面指南規定的標準的外觀和行爲。框架
InterfaceBuilder 把對這些對象的描述保存在 nibfiles 文件中。這些文件包含歸檔的 Objective-C 類和對象表示。當你啓動一個應用程序,並加載一個nibfile,Cocoa 運行時(runtime)在內存中建立這些Objective-C 對象,完成任何實例變量引用其餘對象, 這些可能已被保存在 nibfile 文件中。總之,nibfile是一個已歸檔的用戶界面對象集合,Cocoa 可以快速,輕鬆地在內存中把它復甦。函數
Objective-C 程序員使用 nibfiles 通常的方式是將它們存儲在應用程序束(bundle)中。應用程序的Info.plist 文件(也存儲在 bundle)的指定哪一個 nibfile 是應用程序的主要 nibfile,應用程序啓動時自動加載該文件。應用程序也能夠從 bundle 進行方法調用動態加載其餘 nibfiles。工具
經過 Clozure CL 編寫的 Lisp 應用程序也能夠以一樣時尚的方式來使用 nibfiles(見 「currency-converter」 HOWTO 「Cocoa」 的例子文件夾中),但Lisp程序員習慣於高度互動的開發,並可能想在一個運行着的Clozure CL 會話中簡單地加載任意一個 nibfile 文件。幸運的是,這很是容易。開發工具
讓咱們開始從 Clozure CL 的 Listener 窗口加載一個很是簡單的nibfile。經過啓動 Clozure CL 應用程序來開始。測試
在這份 HOWTO 文件相同的目錄,你會發現一個 nibfile 名爲 「hello.nib」。這是一個極其簡單的nibfile, 它建立了一個帶着一條單一問候語的 Cocoa 窗口。咱們將使用輸入到 Listener 窗口的 Lisp 形式來加載它。
咱們要調用 Objective-C 的類方法 loadNibFile:externalNameTable:withZone: 來加載 nibfile 到內存中,按照文件中的描述來建立窗口。不過,首先,咱們須要創建一些數據結構,咱們將把這些數據結構傳遞給這個方法。
loadNibFile:externalNameTable:withZone: 的參數就是一個路徑名,一個字典對象,以及一個內存區域。隨着每一個 Objective-C 的方法調用,咱們還把收到的消息傳遞給對象,在這種狀況下是類 NSBundle的對象。
譯者注: 這裏須要瞭解 Objective-C 的類方法命名規則, 我也不是特別清楚, 明天學通了再詳述
路徑名僅僅是一個咱們要加載的 nibfile 的引用。這個字典持有對象的引用。在這個簡單的例子裏,咱們將使用它以識別 nibfile 的全部者,在這種狀況下,全部者就是應用程序自己。該區域是對即將分配給 nibfile 對象的內存區域的引用。
不要擔憂,若是你對以上所述都無感的話, 用來建立這些對象的代碼是簡單明瞭的,並應有助於澄清這是怎麼回事。
首先,咱們將獲得一個存儲區。咱們會告訴 Cocoa 在應用程序使用的同一區域中分配 nibfile 對象,所以經過詢問應用程序正在使用的那個區域來獲取一個區域是一件簡單的事情。
咱們能夠要求任何應用以前,咱們須要一個指向它的引用。當 Clozure CL 應用程序啓動時,它把一個指向 Cocoa 應用程序對象的引用存儲到一個特殊變量*NSApp*中。
從改變爲 CCL 包開始; 咱們將使用的大部分的實用功能都被定義在這個包中:
? (in-package :ccl) #<Package "CCL">
咱們得到一個運行中的 Clozure CL 應用程序對象的引用在特殊變量*NSApp*\中。咱們能夠詢問它的區域,也就是它在內存中分配對象的區域:
? (setf *my-zone* (#/zone *NSApp*)) #<A Foreign Pointer #x8B000>
如今咱們有一個應用程序的區域,這是咱們須要傳遞給 loadNibFile:externalNameTable:withZone 的參數之一。
(譯者注: 其實就是得到了一個地址變量, 這個地址變量指向這個應用程序在內存中的入口地址)
loadNibFile:externalNameTable:withZone: 的字典參數用於兩個目的:識別 nibfile 的屬主,並收集頂層(toplevel)對象。
本 nibfile 的屬主變成僱主的頂層對象中建立加載的nibfile時,對象如窗口,按鈕,等等。一個nibfile的全部者管理的對象加載時建立nibfile的,併爲您的代碼提供了一種方法來對這些對象的引用。您提供一個全部者對象字典,根據鍵「NSNibOwner」,。
頂層的對象都是對象,如窗戶,被加載時建立的nibfile。爲了收集這些,你能夠傳遞一個NSMutableArray對象下的關鍵NSNibTopLevelObjects。
對於這第一個例子中,咱們將經過一個全部者對象(應用程序對象),但咱們並不須要收集頂層的對象,因此咱們會省略NSNibTopLevelObjects,關鍵。
? (setf *my-dict* (#/dictionaryWithObject:forKey: ns:ns-mutable-dictionary *my-app* #@"NSNibOwner")) #<NS-MUTABLE-DICTIONARY { NSNibOwner = <LispApplication: 0x1b8e10>; } (#x137F3DD0)> 譯者注: 這段代碼文檔寫錯了, 裏面的那個 *my-app* 要改成 *NSApp* 才能夠獲得後面的輸出
如今,咱們有了咱們須要的區域和字典,咱們能夠加載 nibfile。咱們只須要以正確的路徑名建立一個NSString 就能夠了:
? (setf *nib-path* (%make-nsstring (namestring "~/LispBox-0.92/ccl-1.8-darwinx86//examples/cocoa/nib-loading/hello.nib"))) #<NS-MUTABLE-STRING "/Users/admin/LispBox-0.92/ccl-1.8-darwinx86/examples/cocoa/nib-loading/hello.nib" (#x4B4CC60)> ?
如今,咱們能夠實際加載 nibfile,傳遞咱們已經建立對象的方法:
? (#/loadNibFile:externalNameTable:withZone: ns:ns-bundle *nib-path* *my-dict* *my-zone*) T
譯者注: 成功後會彈出一個小窗口, 以下圖所示:
「hello.nib」 文件中定義的窗口應該出如今屏幕上。 loadNibFile:externalNameTable:withZone: 方法返回T來表示它成功地加載了 nibfile,若是它失敗了,它將返回NIL。
在這一點上,咱們再也不須要路徑名和字典對象。 *nib-path* 咱們必須釋放:
? (setf *nib-path* (#/release *nib-path*)) NIL
*my-dict* 實例沒有被 #/alloc (或者被 MAKE-INSTANCE) 建立,因此這已是自動釋放,咱們不須要再次釋放。
=====已經快12點了, 明天繼續 =====續完
加載一個 nibfile 彷佛就像咱們可能要反覆地作的事情,因此儘量讓它變得容易是有道理的。讓咱們作一個單一的函數,咱們能夠根據須要,調用它來加載一個 nib。
nib-loading 函數能夠把 nib 文件做爲一個參數來加載,而後按照上一節中所列的步驟序列執行。若是咱們僅僅按照字面意思去作,寫出來的函數代碼會是這個樣子:
(defun load-nibfile (nib-path) (let* ((app-zone (#/zone \*NSApp\*)) (nib-name (%make-nsstring (namestring nib-path))) (dict (#/dictionaryWithObject:forKey: ns-mutable-dictionary app #@"NSNibOwner"))) (#/loadNibFile:externalNameTable:withZone: ns:ns-bundle nib-name dict app-zone)))
使用這個函數的麻煩是,每次咱們調用它都會泄漏字符串。返回前咱們須要釋放 nib-name。因此, 看看下面這個替代的版本如何?
(defun load-nibfile (nib-path) (let* ((app-zone (#/zone \*NSApp*)) (nib-name (%make-nsstring (namestring nib-path))) (dict (#/dictionaryWithObject:forKey: ns-mutable-dictionary app #@"NSNibOwner")) (result (#/loadNibFile:externalNameTable:withZone: ns:ns-bundle nib-name dict app-zone))) (#/release nib-name) result))
這個版本解決了泄漏問題,辦法是: 把調用 loadNibFile:externalNameTable:withZone: 的結果綁定到 result,而後在返回調用結果以前,釋放了 nib-name。
只是有一個問題:若是咱們想用字典來收集 nibfile 的頂層對象,這樣咱們就能夠從咱們的代碼訪問到它們?咱們須要函數的另外一個版本。
爲了收集頂層對象,咱們將要傳遞 NSNibTopLevelObjects 給字典,它被存儲在鍵NSMutableArrayObjects。所以,咱們首先須要在 let 形式體裏建立這樣一個數組對象:
(let* (... (objects-array (#/arrayWithCapacity: ns:ns-mutable-array 16)) ...)
...)
如今,咱們有存儲 nibfile 頂層對象的數組,咱們須要修改建立字典的代碼,使其不只包含屬主對象,也包含咱們剛剛建立的數組:
(let* (... (dict (#/dictionaryWithObjectsAndKeys: ns:ns-mutable-dictionary app #@"NSNibOwner" objects-array #&NSNibTopLevelObjects +null-ptr+)) ...) ...)
咱們如今要把對象收集起來。咱們會建立一個局部變量來存儲它們,而後遍歷數組對象把他們全都弄到。 (一般狀況下,當咱們要保持一個對象數組,咱們必須把它保留下來。頂層 nib 對象是一種特殊狀況:它們是由 nib 加載進程建立, 保留計數爲1(a retain count of 1),當咱們經過它們時咱們負責釋放它們)。
(let* (... (toplevel-objects (list)) ...) (dotimes (i (#/count objects-array)) (setf toplevel-objects (cons (#/objectAtIndex: objects-array i) toplevel-objects))) ...)
收集對象後,就能夠釋放該數組,而後返回對象的列表。咱們可能會想知道調用是否成功,仍然是可能的,因此咱們使用變量 values 來返回頂層對象以及調用成功或失敗。
nib-loading 代碼的最終版本看起來像這樣:
(defun load-nibfile (nib-path) (let* ((app-zone (#/zone \*NSApp\*)) (nib-name (%make-nsstring (namestring nib-path))) (objects-array (#/arrayWithCapacity: ns:ns-mutable-array 16)) (dict (#/dictionaryWithObjectsAndKeys: ns:ns-mutable-dictionary \*NSApp\* #@"NSNibOwner" objects-array #&NSNibTopLevelObjects +null-ptr+)) (toplevel-objects (list)) (result (#/loadNibFile:externalNameTable:withZone: ns:ns-bundle nib-name dict app-zone))) (dotimes (i (#/count objects-array)) (setf toplevel-objects (cons (#/objectAtIndex: objects-array i) toplevel-objects))) (#/release nib-name) (values toplevel-objects result)))
如今,咱們能夠拿一些合適的 nibfile 做爲參數來調用這個函數,好比這個 HOWTO 文檔中簡單的的「hello.nib」:
? (ccl::load-nibfile "~/LispBox-0.92/ccl-1.8-darwinx86//examples/cocoa/nib-loading/hello.nib") (#<NS-WINDOW <NSWindow: 0x5b9810> (#x5B9810)> #<LISP-APPLICATION <LispApplication: 0x1f8be0> (#x1F8BE0)>) T ?
「hello!」 窗口出如今屏幕上,而且兩個值被返回。第一個值是已加載的頂層對象列表。第二個值,T表示,已成功加載 nibfile。
Cocoa 沒有提供通用的的 nibfile-unloading API。替代方案是,若是你要卸載一個 nib,可接受的方法是關閉全部跟 nibfile 相關的窗口,並釋放全部頂層對象。這是一個緣由,你可能要對你傳給 loadNibFile:externalNameTable的:withZone: 的字典對象使用 「NSNibTopLevelObjects」 鍵--來得到一個頂層對象集合在再也不須要 nibfile 時釋放這些對象。
在基於文檔的 Cocoa 應用程序,主 nibfile 的屬主一般是應用程序對象,而且在應用程序運行時,主 nibfile 永遠不會被卸載。 副 nibfiles 的屬主通常是控制器對象,一般是 NSWindowController 子類的實例。當你使用 NSWindowController 對象加載 nibfiles 時,他們負責加載和卸載 nibfile 對象。 (譯者注: 原文中拼寫錯誤 Auxliliary , 正確應爲 Auxiliary)
當你試驗交互地加載 nibfile 時,您可能沒法由建立 NSWindowController 對象加載 nibfiles 來開始,因此你可能須要本身手動作更多的對象管理。一方面,手動加載 nibfiles 多是主要的應用程序的問題的來源。另外一方面,若是您在交互式會話中長期試用 nib-loading ,極可能隨着對生存着的而且可能被釋放的對象的各類引用,你會被許多丟棄的對象塞滿內存而退出。在使用 Listener 探索 Cocoa 時請務必記住這一點時。經過重啓 Lisp 您能夠隨時讓您的 Lisp 系統恢復到一個乾淨的狀態,可是理所固然地,你將失去在探索中創建的任何狀態。它每每是一個好主意,在一個文本文件上工做,而不是直接在 Listener 上工做,讓你有一個你所作過試驗的記錄。這樣的話,若是你須要從新開始(或者,若是你不當心會致使應用程序崩潰),你不會失去你已經得到的全部信息。
試驗環境須要自行編譯的 CCL-IDE 版本(Cocoa-IDE), 或者使用從蘋果 APP STORE 下載的 Clozure CL 的 dmg 安裝版本也能夠, 非 IDE 版本暫時還沒搞定, 好比 Emacs 使用的那個命令行的 CCL 版本, 文件名爲 dx86cl64 , 這是由於默認編譯出來的非 IDE 版本的特性裏沒有對 Cocoa 和 Objectiv-C 的支持, 輸入 *features* 就能夠看到不一樣版本支持哪些特性,以下:
1 unix 終端窗口命令行用的版本支持特性:
Air:ccl-1.8-darwinx86 admin$ ./dx86cl64 Welcome to Clozure Common Lisp Version 1.8-r15286M (DarwinX8664)! ? \*features\* (:PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :DARWIN-HOST :DARWIN-TARGET :DARWINX86-TARGET :DARWINX8664-TARGET :DARWINX8664-HOST :64-BIT-TARGET :64-BIT-HOST :DARWIN :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST) ?
2 Emacs 用的版本支持的特性:(比前者多了對 slime 的支持)
CL-USER> \*features\* (:SWANK :PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :DARWIN-HOST :DARWIN-TARGET :DARWINX86-TARGET :DARWINX8664-TARGET :DARWINX8664-HOST :64-BIT-TARGET :64-BIT-HOST :DARWIN :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST) CL-USER>
3 自行編譯的 Cocoa-IDE 版本支持的特性:
? \*features\* (:EASYGUI :ASDF2 :ASDF :HEMLOCK :APPLE-OBJC-2.0 :APPLE-OBJC :PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :DARWIN-HOST :DARWIN-TARGET :DARWINX86-TARGET :DARWINX8664-TARGET :DARWINX8664-HOST :64-BIT-TARGET :64-BIT-HOST :DARWIN :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST) ?
4 蘋果 APP STORE 下載的 dmg 版本支持的特性:
? \*features\* (:EASYGUI :ASDF2 :ASDF :HEMLOCK :APPLE-OBJC-2.0 :APPLE-OBJC :PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :DARWIN-HOST :DARWIN-TARGET :DARWINX86-TARGET :DARWINX8664-TARGET :DARWINX8664-HOST :64-BIT-TARGET :64-BIT-HOST :DARWIN :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST) ?
容易看出, 主要的區別是前面, 後面基本同樣.