=== 原文地址: 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) ?
容易看出, 主要的区别是前面, 后面基本同样.