Clozure Common Lisp 帮助文档-第14章 Objective-C 桥(中文版)

Clozure Common Lisp 帮助文档-第14章 Objective-C 桥(中文版)

=== 原文地址: 网络: http://ccl.clozure.com/ccl-documentation.html
原文标题: Clozure CL Documentation 翻译者: FreeBlues 2013-08-03html

===数据库

目录

14 Objective-C 桥 the Objective-C Bridge

特别说明: 本章节我也是边学边翻, 很多地方没有理解, 致使翻译过来的中文读着也是磕磕绊绊, 欢迎各位朋友指正,谢谢!安全

Mac OS X 的 API 使用名为 Objective-C 的语言,这是在 C 的基础上增长了一些仿照 Smalltalk 的面向对象的扩展。 Objective-C 的桥能够从 Lisp 使用 Objective-C 的对象和类,并在 Lisp 中定义能够用于 Objective-C 的类。网络

Objective-C 和 Cocoa 桥的最终目的是使得 Cocoa(Mac OS X上的标准用户界面框架)易于从 Clozure CL 中使用 ,以支持在 Mac OS X(在任何平台上,支持 Objective-C 语言,如 GNUstep)上开发 GUI 应用程序和集成开发环境(IDE) 。最终的目标,比之前更为接近,是把 Cocoa 彻底集成到CLOS 中。数据结构

当前的版本提供相似 Lisp 语法和命名约定的 Objective-C 的基本操做,以及自动类型处理和编译时的消息有效性检查。在用 Cocoa 工做时它也提供了一些便利功能。app

14.1 版本 1.2 里的变化 Changes in 1.2

1.2版的 Clozure CL 导出了大多数本章中描述的最有用的符号,在之前的版本中,其中大部分在 CCL 包中是 private 的。框架

有一些新的读取宏(reader macro),使它比之前更方便于引用类的符号用于 Objective-C 的桥。对于这些读取宏的完整说明,请参阅13.12 外部函数接口字典 FFI Dictionary,尤为是项目开始时,关于读取宏的描述。函数

在之前的版本,32位版本的 Clozure CL 使用32位的浮点数和整数数据结构描述的几何形状,字体大小和度量,等等。64位版本的 Clozure CL 在适当状况下使用64位值。字体

Objective-C 桥定义类型 NS:CGFLOAT 做为当前平台上的首选浮点类型类的 Lisp 类型,并定义常量 NS:+CGFLOAT+。 在 DarwinPPC32 中,外部类型 :cgfloat,:<NSUI>nteger, 以及 :<NSI>nteger 被 Objective-C 桥分别定义为(32位浮点,32位无符号整数,32位有符号整数); 这些类型在64位的接口被定义为64位变体。优化

如今每一个 Objective-C 类被正确命名,不管是从 NS 包(在一个接口文件中声明中预约义的类的状况下)或提供的名称在 DEFCLASS 形式导出的名称(经过 :MetaClass NS:+NS-OBJECT)从 Lisp 定义类。 类的 Lisp 名字如今自称为一个“静态”变量(仿佛经过 DEFSTATIC,在“4.8 静态变量”一节)和类对象做为它的价值。换句话说:

(send (find-class 'ns:ns-application) 'shared-application)

(send ns:ns-application 'shared-application)

是等价的. (既然绑定一个“静态”变量是不合法的, 它可能有必要进行重命名一些东西, 这样使一些名字碰巧跟 Objective-C 类名冲突的无关变量都不这样作)

14.2 使用 Objective-C 的类 Using Objective-C Classes

最标准的 CLOS 类的类被命名为 STANDARD-CLASS. 在 Objective-C 的对象模型, 每一个类是一个(一般是惟一的) metaclass 的一个实例, 它自己就是一个 “base” metaclass(一般该类的 metaclass 被名为 “NSObject”). 所以, Objective-C 类被命名为 "NSWindow" 而且 "Objective-C" 类 "NSArray" 是(sole) 它们的一样名为 "NSWindow" 和 "NSArray" 的不一样的 meataclass 的实例. (在 Objective-C 世界里, 像实例分配同样指定类的行为, 是更为常见和有用的)

当 Clozure CL 首次加载含 Objective-C 类的外部库时, 它会识别它们包含的类. 外部类名, 如 "NSWindow" 经过桥的转换规则被映射到一个 "NS" 包里的外部符号--NS:NS-WINDOW. 一个相似的变形会发生在 metaclass 名上, 前面加上一个 "+", 产生相似于这样的形式 NS:+NS-WINDOW.

这些被集成到 CLOS 里的类, 如 metaclass 是类 OBJC:OBJC-METACLASS 的一个实例而且该类也是 metaclass 的一个实例. SLOT-DESCRIPTION metaobjects 被每个实例变量所建立, 而且类和 metaclass 经过一些很是类似的“标准” CLOS 类初始化协议(与别不一样的是,这些类已被分配)

当你 (require "COCOA") 会在当前花费几秒钟来执行全部这些初始化. 加快的念头能够想一想,但这是毫不可能加快的.

当该过程完成后, CLOS 清楚认识几百个新的 Objective-C 类和它们的元类. Clozure CL 的运行时系统能够可靠地识别 Objective-C 类 MACPTRs 为类对象, 而且能(至关可靠但启发式地)识别这些类(虽然这里有复杂的因素, 见下文)的实例. SLOT-VALUE 能被用来访问(以及当心地设置) Objective-C 实例中的实例变量. 为了演示这些, 请看下面示例:

? (require "COCOA")
"COCOA"
NIL
?

接着, 在等待时间稍微长一点的一个 Cocoa listener 窗口出现后,激活 Cocoa listener 而且这样作:

? (describe (ccl::send ccl::*NSApp* 'key-window))
#<HEMLOCK-LISTENER-FRAME <HemlockListenerFrame: 0x10a8f20> (#x10A8F20)>
Class: #<OBJC:OBJC-CLASS GUI::HEMLOCK-LISTENER-FRAME (#x152340)>
Wrapper: #<CLASS-WRAPPER GUI::HEMLOCK-LISTENER-FRAME #x302000C3FF7D>
Instance slots
GUI::ECHO-AREA-BUFFER: #<Hemlock Buffer "Echo Area 1">
GUI::ECHO-AREA-STREAM: NIL
NS:ISA: #<OBJC:OBJC-CLASS GUI::HEMLOCK-LISTENER-FRAME (#x152340)>
NS:_NEXT-RESPONDER: #<HEMLOCK-LISTENER-WINDOW-CONTROLLER <HemlockListenerWindowController: 0x1478b60> (#x1478B60)>
NS:_FRAME: #<NS-RECT 892 X 784 @ 575,60 (#x10A8F30) #x302000EE923D>
NS:_CONTENT-VIEW: #<NS-VIEW <NSView: 0x146a460> (#x146A460)>
NS:_DELEGATE: #<HEMLOCK-LISTENER-WINDOW-CONTROLLER 	<HemlockListenerWindowController: 0x1478b60> (#x1478B60)>
NS:_FIRST-RESPONDER: #<HEMLOCK-TEXT-VIEW <HemlockTextView: 0x637df0>
    Frame = {{0.00, 0.00}, {892.00, 3439.00}}, Bounds = {{0.00, 0.00}, {892.00, 3439.00}}
    Horizontally resizable: NO, Vertically resizable: YES
    MinSize = {892.00, 727.00}, MaxSize = {10000000.00, 10000000.00}
 (#x637DF0)>
NS:_LAST-LEFT-HIT: #<HEMLOCK-TEXT-VIEW <HemlockTextView: 0x637df0>
    Frame = {{0.00, 0.00}, {892.00, 3439.00}}, Bounds = {{0.00, 0.00}, {892.00, 3439.00}}
    Horizontally resizable: NO, Vertically resizable: YES
    MinSize = {892.00, 727.00}, MaxSize = {10000000.00, 10000000.00}
 (#x637DF0)>
NS:_LAST-RIGHT-HIT: #<A Null Foreign Pointer>
NS:_COUNTERPART: #<A Null Foreign Pointer>
NS:_FIELD-EDITOR: #<A Null Foreign Pointer>
NS:_WIN-EVENT-MASK: -1071906816
NS:_WINDOW-NUM: 5967
NS:_LEVEL: 0
NS:_BACKGROUND-COLOR: #<NS-COLOR NSCalibratedWhiteColorSpace 1 1 (#x519EC0)>
NS:_BORDER-VIEW: #<NS-VIEW <NSThemeFrame: 0x10265c0> (#x10265C0)>
NS:_POSTING-DISABLED: 0
NS:_STYLE-MASK: 15
NS:_FLUSH-DISABLED: 0
NS:_RESERVED-WINDOW-1: 0
NS:_CURSOR-RECTS: #<A Foreign Pointer #x149FF00>
NS:_TRECT-TABLE: #<A Foreign Pointer #x103BD20>
NS:_MINI-ICON: #<A Null Foreign Pointer>
NS:_UNUSED: 1
NS:_DRAG-TYPES: #<A Null Foreign Pointer>
NS:_REPRESENTED-URL: #<A Null Foreign Pointer>
NS:_SIZE-LIMITS: #<A Null Foreign Pointer>
NS:_FRAME-SAVE-NAME: #<NS-MUTABLE-STRING "Untitled" (#x17DB3C0)>
NS:_REG-DRAG-TYPES: #<NS-MUTABLE-SET {(
    "NeXT font pasteboard type",
    NSStringPboardType,
    "NSColor pasteboard type",
    "Apple URL pasteboard type",
    "Apple PNG pasteboard type",
    "Apple HTML pasteboard type",
    "NeXT ruler pasteboard type",
    "Apple PDF pasteboard type",
    "public.url",
    WebURLsWithTitlesPboardType,
    "Apple PICT pasteboard type",
    "NeXT RTFD pasteboard type",
    "NeXT Encapsulated PostScript v1.2 pasteboard type",
    "NeXT TIFF v4.0 pasteboard type",
    NSFilenamesPboardType,
    "CorePasteboardFlavorType 0x6D6F6F76",
    "NeXT Rich Text Format v1.0 pasteboard type"
)} (#x634FE0)>
NS:_W-FLAGS: #<A Foreign Pointer (:* (:STRUCT :__W<F>LAGS)) #x10A9000>
NS:_DEFAULT-BUTTON-CELL: #<A Null Foreign Pointer>
NS:_INITIAL-FIRST-RESPONDER: #<NS-VIEW <NSView: 0x146a460> (#x146A460)>
NS:_AUXILIARY-STORAGE: #<A Foreign Pointer (:*
                                            (:STRUCT
                                             :<NSW>INDOW<A>UXILIARY))#x1028320>
GUI::ECHO-AREA-VIEW: #<ECHO-AREA-VIEW <EchoAreaView: 0x1476580>
    Frame = {{0.00, 0.00}, {876.00, 20.00}}, Bounds = {{0.00, 0.00}, {876.00, 20.00}}
    Horizontally resizable: YES, Vertically resizable: NO
    MinSize = {876.00, 20.00}, MaxSize = {10000000.00, 10000000.00}
 (#x1476580)>
GUI::PANE: #<TEXT-PANE <TextPane: 0x51aa40> (#x51AA40)>
?

这样发出一个消息, 要求关键窗口, 就是具备输入焦点的窗口(常常在最前面), 而后描述它. 正如咱们能够看到的, NS:NS-WINDOWS 有不少有趣的槽。

14.3 实例化 Objective-C 对象 Instantiating Objective-C Objects

实现一个 Objective-C 类(不论该类是否被预约义或由应用程序定义过)的一个实例,包括经过类和做为参数的一组 initargs 来调用 MAKE-INSTANCE. 正如 STANDARD-CLASS,实现一个包括初始化(用 INITIALIZE-INSTANCE)一个经过 ALLOCATE-INSTANCE 分配的对象的实例。

例如,您能够像这样建立一个 ns:ns-number :

? (make-instance 'ns:ns-number :init-with-int 42)
#<NS-CF-NUMBER 42 (#x85962210)>

奇怪, 译者的环境是这个结果:
? (make-instance 'ns:ns-number :init-with-int 42)
#<A Null Foreign Pointer>
?

若是你用 Objective-C 来编写, 如何作到这一点是值得期待的:

[[NSNumber alloc] initWithInt: 42]

分配一个 Objective-C 类的一个实例包括发送给这个类一个 “alloc” 消息, 而后利用这些做为“初始化”消息被发送到新分配的实例的不对应插槽 initargs 的 initargs. 因此,上面的例子能够被作得更冗长:

? (defvar *n* (ccl::send (find-class 'ns:ns-number) 'alloc))
*N*
? 

? (setq *n* (ccl::send *n* :init-with-int 42))
#<NS-NUMBER 42 (#x2A83)>
?

setq 同样是很重要的,这是一个初始化决定替换对象并返回新对象,而不是修改现有对象的场景. 事实上,若是你略去 setq,而后尝试查看 *N* 的值,Clozure CL 将冻结. 不多有理由来这么作; 这只是代表这是怎么回事。

你见过一个 Objective-C 初始化方法并不是必须返回跟它所传递的相同的对象. 事实上, 它根本不必必须返回任何对象; 在这种状况下,初始化失败,make-instance 返回nil。

在某些特殊状况下, 如从一个 .nib 文件加载一个 ns:ns-window-controller, 传递实例自己做为初始化方法的参数之一对你来讲多是必要的. 它是这样的:

? (defvar *controller*
      (make-instance 'ns:ns-window-controller))
*CONTROLLER*
?

? (setq *controller*
      (ccl::send *controller*
      :init-with-window-nib-name #@"DataWindow"
      :owner *controller*))
#<NS-WINDOW-CONTROLLER <NSWindowController: 0xc283d00> (#xC283D00)>
?

此示例不带 initargs 调用 (make-instance). 当你这么作,对象仅仅被分配,没有被初始化。而后它发送 “init” 消息来手工初始化。

有一种替代的 API 用于实例化 Objective-C 类. 你能够调用 OBJC:MAKE-OBJC-INSTANCE, 把 Objective-C 类名做为一个字符串传递给它. 在之前的版本中, 在类没有定义任何 Lisp 槽的状况下, OBJC:MAKE-OBJC-INSTANCE 能比 OBJC:MAKE-INSTANCE 实现更高的效率, 如今再也不是那样了. 如今您能够把 OBJC:MAKE-OBJC-INSTANCE 和 OBJC:MAKE-INSTANCE 看作彻底等效,除非你能够传递类名字符串--有利于这种场景:在某种程度上类名不太常见。

14.4 调用 Objective-C 方法 Calling Objective-C Methods

在 Objective-C 中, 方法被称为"消息", 而且有一种特定的语法用来给一个对象发送一条消息:

[w alphaValue]
[w setAlphaValue: 0.5]
[v mouse: p inRect: r]

第一行发送方法 "alphaValue" 给对象 w, 不带参数. 第二行发送方法 "setAlphaValue", 带参数 0.5. 第三行发送方法 "mouse:inRect:" - 是的,就是这长长的一串内容- 带着参数 p 和 r.

在 Lisp 中, 一样的三行以下:

(send w 'alpha-value)
(send w :set-alpha-value 0.5)
(send v :mouse p :in-rect r)

请注意,当一个方法没有参数,它的名字是一个普通的符号(没关系的符号是什么样的包,只是它的名称被选中)。当一个方法的参数,其名称的一部分是一个关键字,关键字的值交替。

这两行语句打破这些规则,都将致使在错误消息:

(send w :alpha-value)
(send w 'set-alpha-value 0.5)

除了(send),你也能够调用(send-super),具备相同的接口。 CLOS(call-next-method)它具备大体相同的目的,当你用(send-super),该消息被超类处理。这能够用来在原来实行的一种方法,被子类中的一个方法屏蔽(shadow)时。

14.4.1. Objective-C 方法调用的类型强制 Type Coercion for Objective-C Method Calls

Clozure CL 的 FFI 处理不少 Lisp 和外部数据之间的公共约定, 如拆箱(unboxing)浮点参数和装箱(boxing)浮点结果. 桥增长了一些更自动化的约定:

NIL 等价于 (%NULL-PTR) , 对于任何须要一个指针的消息参数

T/NIL 等价于 #$YES/#$NO , 对于任何布尔参数

被任何返回 BOOL 类型的方法返回的一个 #$YES/#$NO 将会被自动转换为 T/NIL.

14.4.2. 返回结构的方法 Methods which Return Structures

一些 Cocoa 方法返回小结构, 好比用于 表示点, 区域, 大小和范围. 当用 Objective-C 写代码时, 编译器隐藏了实现的细节. 不幸的是, 在用 Lisp 写代码时咱们必须对它们知道得更清楚明白.

返回结构的方法被以一种特别的方式调用; 调用者为结果分配空间, 而且把一个指针当作一个附加的参数传递给这个方法. 这被称为一个结构返回, 或者 STRET. 不要瞪我, 这些名字不是我起的. :)

这里有一个 Objective-C 对此的简单使用. 第一行发送 "bounds" 消息给 v1, 它返回一个 rectangle. 第二行发生 "setBounds" 消息给 v2, 传递一样的做为一个参数的 rectangle.

NSRect r = [v1 bounds];
[v2 setBounds r];

在 Lisp 中, 咱们必须明确地分配内存, 由 rlet 最轻易,最安全地完成. 咱们这么作:

(rlet ((r :<NSR>ect))
	(send/stret r v1 'bounds)
	(send v2 :set-bounds r))

rlet 分配了存储(可是没有初始化), 而且确认当咱们处理时它会被释放掉. 它绑定变量 r 来指向它. send/stret 的调用就像一个正常的 send 调用, 除了 r 会被传递给一个附加的第一个参数. 第三行, 调用 send , 不须要作任何特别的事, 由于把一个结构做为一个参数来传递没有什么复杂的.

为了让 STRETs 更易于使用, 桥提供了两个约定.

首先, 你能够在一步内使用宏 slet 和 slet* 来分配和初始化局部变量为外部结构. 下面的示例能够写的更简洁:

(slet ((r (send v1 'bounds)))
	(send v2 :set-bounds r))

其次, 当一个 send 调用被建立在另外一个内部时, 内部的那个有一个围绕它的隐含的SLET. 所以, 代码实际能够这么写:

(send v1 :set-bounds (send v2 'bounds))

有一些由 Objective-C 编译器提供于约定的虚拟函数(pseudo-functions), 可使对象成为特定的类型. 下面是目前桥所支持的: NS-MAKE-POINT, NS-MAKE-RANGE, NS-MAKE-RECT, and NS-MAKE-SIZE.

These pseudo-functions can be used within an SLET initform: 这些虚拟函数能够被用于一个 SLET 的 initform 内:

(slet ((p (ns-make-point 100.0 200.0)))
      (send w :set-frame-origin p))

或者在一个 send 调用内:

(send w :set-origin (ns-make-point 100.0 200.0))

然而, 既然这里没有实际函数, 一个相似下面的调用是不会工做的:

(setq p (ns-make-point 100.0 200.0))

为了从对象中提取字段, 这里也有一些约定宏: NS-MAX-RANGE, NS-MIN-X, NS-MIN-Y, NS-MAX-X, NS-MAX-Y, NS-MID-X, NS-MID-Y, NS-HEIGHT, and NS-WIDTH.

注意, 这里还有一种用于方法内部的 send-super/stret. 就像 send-super, 会忽略任何在一个子类里被屏蔽的方法, 而后调用属于它的父类的方法的版本.

14.4.3. 参数数量可变的消息 Variable-Arity Messages

Cocoa 中有一些消息的参数个数可变. 或许最多见的例子包括格式化字符串:

[NSClass stringWithFormat: "%f %f" x y]

在 Lisp 中, 将会被写做:

(send (find-class 'ns:ns-string)
      :string-with-format #@"%f %f"
      (:double-float x :double-float y))

注意, 必须指出变量(在这个例子里是 :double-float)的外部类型, 由于编译器没法经过正常途径来知道这些类型.(你可能认为它能够分析格式化字符串, 可是这种状况仅仅在格式化字符串没有在运行时中被肯定时才有效)

由于 Objective-C 的运行时系统没有提供消息是参数数量可变的任何信息, 它们必须被明确地声明. Cocoa 中标准的参数数量可变消息在桥中会被预声明. 若是你须要声明一个新的参数数量可变消息, 请使用: (DEFINE-VARIABLE-ARITY-MESSAGE "myVariableArityMessage:").

14.4.4. 优化 Optimization

即便获取了足够的信息, 桥在优化消息发送方面仍然工做得极其艰难. 在它工做时这有两个缘由. 任一种缘由, 一个消息发送应该差很少和用 Objective-C 书写代码效率相近.

第一个缘由是当消息和接收器的类都在编译时可知. 通常地, 知道接收器的类的仅有办法是若是你或者使用 DECLARE 或者使用一个 THE 形式来声明它. 例如:

(send (the ns:ns-window w) 'center)

注意, 在 Objective-C 中是没有办法去命名一个类的类. 于是桥提供了一个声明 @METACLASS. "NSColor" 的一个实例的类型是 ns:ns-color. 类 "NSColor" 的类型是 (@metaclass ns:ns-color):

(let ((c (find-class 'ns:ns-color)))
  (declare ((ccl::@metaclass ns:ns-color) c))
  (send c 'white-color))

其余容许优化的缘由是仅仅当消息在编译时可知, 可是它的类型签名是惟一的. 在当前超过 6000 个由 Cocoa 提供的消息中, 仅仅有 50 个拥有非惟一的类型签名.

关于一个带有类型签名的消息不是惟一的例子是 SET. 它返回 VOID 给 NSColor, 可是返回 ID 给 NSSet. 为了优化带有非惟一类型签名的消息发送, 接收器的类必须在编译时被声明.

若是类型签名是非惟一或者消息是未知, 在编译时, 那么一个更慢得运行时调用必须被使用.

当接收器的类未知, 桥的优化能力依赖于一个它所维护的类型签名表. 当第一个被加载, 桥经过扫描每一个 Objective-C 类的每一个方法来初始化这个表. 当新方法在此以后被定义, 这个表必须被更新. 这些都是当你在 Lisp 中定义方法时自动发生的. 在任何其余重要的改变以后, 好比加载一个外部框架, 你须要执行以下命令来重建这个表:

? (update-type-signatures)

由于 send 以及和它相关的 send-super, send/stret, 还有 send-super/stret 都是宏, 因此它们不能被 funcall 和 apply 调用, 或者当作一个参数传递给函数.(译者注: fuuncall 和 apply 只能调用函数, 不能调用宏)

为了解决这个问题, 这里有一些等效函数: %send, %send-super, %send/stret, 和 %send-super/stret. 不过, 这些函数应该仅被用于宏不被使用的场景, 由于它们没法被优化.

14.5 定义 Objective-C 类 Defining Objective-C Classes

你能定义你本身的外部类, 能够被传递给外部函数; 你在 Lisp 中实现的方法将被做为回调函数提供给外部代码.

你也能够定义已存类的子类, 在 Lisp 实现你的子类, 哪怕父类是在 Objective-C 也能够. 相似的一个子类是 CCL::NS-LISP-STRING. 建立 NS-WINDOW-CONTROLLER 的子类也是至关有用的.

咱们能用 MOP 来定义新 Objective-C 类, 可是咱们不得不作的事情却有些滑稽: 咱们想要在一个 DEFCLASS 选项里使用的 :METACLASS 通常不存在--直到咱们已经建立了那个类(还记得 Objective-C 类有, 为了参数的缘故, 惟一而且私有的 metaclass) 咱们能排出丑陋的解决方法: 经过指定一个已知的
Objective-C metaclass 对象名, 当作 DEFCLASS :METACLASS 对象的值; 根类 NS:NS-OBJECT, NS:+NS-OBJECT 的 metaclass, 作一个好的选择. 建立一个 NS:NS-WINDOW(为了简单起见,不定义任何新的插槽) 的子类, 咱们能够这么写:

(defclass example-window (ns:ns-window)
  ()
  (:metaclass ns:+ns-object))

这样将会建立一个新的 Objective-C 类, 被命名为 EXAMPLE-WINDOW, 它的 metaclass 是名为 +EXAMPLE-WINDOW 的类. 该类将会成为一个类型为 OBJC:OBJC-CLASS 的对象, 而且 metaclass 将的类型将为 OBJC:OBJC-METACLASS. EXAMPLE-WINDOW 将成为 NS-WINDOW 的一个子类.

14.5.1 定义带外部槽的类 Defining classes with foreign slots

若是在一个 Objective-C 类里定义的槽的格式包含关键字 :FOREIGN-TYPE, 这个槽就是一个"外部槽"(例如一个 Objective-C 实例变量). 要知道以任何方式重定义一个 Objective-C 类而致使它的外部槽发生变化都是错误的, 当你尝试这么作时 Clozure CL 不会作任何事来保持一致性.

:FOREIGN-TYPE initarg 的值应该是一个外部类型指示符. 例如, 若是咱们想(出于一些缘由)定义一个 NS:NS-WINDOW 的子类, 能够保持追踪他接收到的(须要一个实例变量来保持这个信息)关键事件的数目, 咱们能够这么写:

(defclass key-event-counting-window (ns:ns-window)
  ((key-event-count :foreign-type :int
                    :initform 0
                    :accessor window-key-event-count))
  (:metaclass ns:+ns-object))

外部槽通常是 SLOT-BOUNDP, 而且上述的 initform 是冗余: 外部槽被初始化为二进制 0

14.5.2 定义带 Lisp 槽的类 Defining classes with Lisp slots

一个 Objective-C 类里定义的槽的格式不包含关键字 :FOREIGN-TYPE initarg, 定义了一个漂亮得多的 Lisp 槽, 碰巧被关联到 "一个外部类的实例". 例如:

(defclass hemlock-buffer-string (ns:ns-string)
  ((hemlock-buffer :type hi::hemlock-buffer
                   :initform hi::%make-hemlock-buffer
                   :accessor string-hemlock-buffer))
  (:metaclass ns:+ns-object))

正如人们所指望, 这里有内存管理含义: 咱们不得不维护介于一个 MACPTR 和一个 lisp 对象(它的槽)集合之间的关联, 只要这个 Objective-C 实例存在, 咱们不得不肯定 Objective-C 实例的存在(没有被调用 -dealloc 方法)当 lisp 试着去把它当作一个当它依然可能被引用而不能被 "deallocated" 的第一类对象去思考. 关联一个或多个 lisp 对象到一个外部实例上每每是颇有用的; 若是你"手动"去作这些, 你不得不面对不少相似的内存管理问题.

14.6 定义 Objective-C 方法 Defining Objective-C Methods

在 Objective-C 中, 不像在 CLOS 中, 每一个方法输入一些特定类. 对于你这或许不是一个奇怪的概念, 由于 C++ 和 Java 也作一样的事. 当你使用 Lisp 去定义 Objective-C 方法时, 只可能定义一些属于 Objective-C 的类而且已经在 Lisp 中被定义好的方法.

你可使用两种不一样宏的任一种来定义 Objective-C 类的方法. define-objc-method 接受一个包含一个消息选择器名和一个类名的二元素列表, 和一个形式体. objc:defmethod 表面上相似于普通的 CLOS 宏 defmethod, 但它实际在 Objective-C 类上新建方法时, 会受到跟使用 define-objc-method 新建方法同样的限制.

14.6.1 使用 define-objc-method 函数 Using define-objc-method

如同在章节 "14.4. Calling Objective-C Methods" 中所描述, Objective-C 的方法名被分红小块, 每一块跟着一个参数. 全部参数的类型都必须被确切地声明.

认真考虑一些例子, 用来讲明 define-objc-method 的使用. 让咱们定义一个类来在它们中使用

(defclass data-window-controller (ns:ns-window-controller)
  ((window :foreign-type :id :accessor window)
   (data :initform nil :accessor data))
  (:metaclass ns:+ns-object))

关于这个类没什么特别的地方. 它继承自 ns:ns-window-controller. 它有两个槽(slots): window 是一个外部槽, 保存在 Objective-C 世界中; data 是一个普通槽, 储存在 Lisp 世界中.

这里有一个实例关于如何定义一个不带任何参数的方法:

(define-objc-method ((:id get-window)
                     data-window-controller)
    (window self))

这个方法的返回值是外部类型 :id, 被用于全部的 Objective-C 对象. 方法名为 get-window. 方法的形式体是单独一行 (window self). 变量 self , 在形式体内部, 被绑定到接收消息的实例上. 对 window 的调用使用 CLOS 的访问器(acessor)来获取 window 字段的值.

Here's an example that takes a parameter. Notice that the name of the method without a parameter was an ordinary symbol, but with a parameter, it's a keyword: 这里有一个带参数的示例. 注意不带参数的方法名是一个普通符号, 可是一旦带一个参数, 它就变成一个关键字了:

(define-objc-method ((:id :init-with-multiplier (:int multiplier))
                     data-window-controller)
  (setf (data self) (make-array 100))
  (dotimes (i 100)
    (setf (aref (data self) i)
          (* i multiplier)))
  self)

对使用类的 Objective-C 代码来讲, 这个方法的名字是 initWithMultiplier:. 参数名是 multiplier, 它的类型是 :int. 方法形式体作了一些毫无心义的事情. 而后返回了 self, 由于这是一个初始化方法.

这里是一个带有多于一个参数的示例:

(define-objc-method ((:id :init-with-multiplier (:int multiplier)
                          :and-addend (:int addend))
                     data-window-controller)
  (setf (data self) (make-array size))
  (dotimes (i 100)
    (setf (aref (data self) i)
          (+ (* i multiplier)
             addend)))
  self)

对于 Objective-C 代码来讲, 方法名是 initWithMultiplier:andAddend:. 两个参数的类型都是 :int; 第一个参数名是 multiplier, 第二个参数名是 addend. 这个方法再次返回 self.

接下来是一个不返回任何值的方法. 也被称为 "空方法"(void method). 也就是其余方法返回值类型为 :id, 这个方法返回值类型为 :void

(define-objc-method ((:void :take-action (:id sender))
                     data-window-controller)
  (declare (ignore sender))
  (dotimes (i 100)
    (setf (aref (data self) i)
          (- (aref (data self) i)))))

这个方法在 Objective-C 中被称为 takeAction:. 做为将被当作 Cocoa 行为使用的这种方法的约定是, 它们使用一个对象负责的用于触发该行为的参数, 因此它明确地忽略它,以免编译器警告. 如同约定所承诺, 这类方法不返回任何值.

还有其余的语法, 如这里所示. 下面两种方法定义时等效的:

(define-objc-method ("applicationShouldTerminate:"
                     "LispApplicationDelegate")
    (:id sender :<BOOL>)
    (declare (ignore sender))
    nil)

(define-objc-method ((:<BOOL>
                      :application-should-terminate sender)
                       lisp-application-delegate)
    (declare (ignore sender))
    nil)

14.6.2 使用 objc:defmethod 函数 Using objc:defmethod

宏 OBJC:DEFMETHOD 可被用来定义 Objective-C 方法. 在某些方面, 它表面上看起来很像 CL:DEFMETHOD

它的语法是:

(OBJC:DEFMETHOD name-and-result-type 
               ((receiver-arg-and-class) &rest other-args) 
      &body body)

name-and-result-type 或者是一个 Objective-C 消息名, 它对应的方法是返回一个 :ID 类型的值, 或者是一个列表, 包含一个带有不一样外部结果类型的方法的 Objective-C 消息名和一个外部类型描述符.

receiver-arg-and-class 是一个二元素列表, 列表的第一个元素是一个变量名, 第二个元素是一个Objective-C 类或 metaclass 的 Lisp 名. 接收器(receiver)变量名能够是任何可被绑定的 Lisp 变量名, 不过 SELF 多是一个合理的选择. 接收器(receiver)变量被声明为"不可设置"(unsettable); 例如, 在方法定义形式体内试图去修改接收器的值是一个错误.

other-args 或者是变量名(参数类型为 :ID), 或者是第一个元素为一变量名第二个元素为一外部类型指示符的二元素列表.

认真思考这个示例:

(objc:defmethod (#/characterAtIndex: :unichar)
    ((self hemlock-buffer-string) (index :<NSUI>nteger))
  ...)

方法 characterAtIndex:, 当被一个 HEMLOCK-BUFFER-STRING 类的对象经过一个类型为 :<NSU>integer 的附加参数调用时, 会返回一个类型为 :unichar 的值.

除一些指针类型之外的参数 :ID(如, 指针, 按值传递的记录)都表示为类型的外部指针,所以更高级别的类型检查访问器(accessors),可用于类型的参数 :ns-rect, :ns-point, 依此类推。

在经过 OBJC:DEFMETHOD 定义的方法体内, 局部函数 CL:CALL-NEXT-METHOD 被定义. 它不像 CL:CALL-NEXT-METHOD 在一个 CLOS 方法内使用那么广泛, 而是有一些相同的语义. 它接受包含方法的 other-args 列表而且调用在接收者的类的带有接收者和其余被提供参数的父类的实例上所调用的包含方法的版本。(传递当前方法的参数到下一个方法的惯例, 已经足够通用了, OBJC:DEFMETHODs 中的 CALL-NEXT-METHOD 应该大体也能作这个若是它没有收到任何参数.)

一个经过 OBJC:DEFMETHOD 定义的方法返回一个结构 "经过值" 可以经过返回一个以 MAKE-GCABLE-RECORD 新建的记录来实现, 可以经过返回一个以 CALL-NEXT-METHOD 新建的值来实现, 或者经过其余相似的手段. 在幕后, 能够是一个预分配好的记录类型实例(用来支持本地结构返回约定), 和经过将会被拷贝到这个内部记录实例中的方法体所返回的任意值. 在一个经过被声明返回一个结构类型的 OBJC:DEFMETHOD 所定义的方法体内部, 局部宏 OBJC:RETURNING-FOREIGN-STRUCT 能够被用于访问内部结构.

例如:

(objc:defmethod (#/reallyTinyRectangleAtPoint: :ns-rect) 
  ((self really-tiny-view) (where :ns-point))
  (objc:returning-foreign-struct (r)
    (ns:init-ns-rect r (ns:ns-point-x where) (ns:ns-point-y where)
                        single-float-epsilon single-float-epsilon)
    r))

若是 OBJC:DEFMETHOD 新建一个新的方法, 接着的效果是它会显示一条消息. 这些消息对于捕获方法定义里的名字的错误颇有用. 另外附带地, 若是一个 OBJC:DEFMETHOD 形式体中经过修改它的类型签名重定义了一个方法, Clozure CL 的情况系统将会升起一个持续的错误.

14.6.3 方法重定义约束 Method Redefinition Constraints

Objective-C 没有像 Lisp 同样, 在构思时就被设计成在运行时支持重定义. 所以对于你如何和什么时候可以替换一个 Objective-C 方法的定义有一些限制. 目前, 若是你违反规则, 什么都不会崩溃, 可是程序的行为将会变得让人疑惑; 因此别那么作.

Objective-C 方法能够被重定义在运行时, 可是它们的签名不会改变. 这就是说, 参数的类型和返回值的类型不得不保持原样. 关于这一点的缘由是修改签名会修改用于调用方法的选择器(selector)

当一个方法已经在一个类中被定义, 而且你把它定义在一个子类里, 屏蔽了原始的方法, 它们必须拥有相同类型的签名. 不存在这样的限制,不过,若是这两个类是不相关的, 而且方法刚好具备相同的名称。

14.7 加载框架 Loading Frameworks

在 Mac OS X 中, 一个框架是一个包含着一个或多个共享库, 连同形如 C 和 Objective-C 头文件的元数据的结构化目录. 有时, 框架能够包含附加项, 如可执行文件.

加载一个框架意味着打开共享库而且处理全部的声明, 所以 Clozure CL 能随之调用入口点并使用它们的数据结构. 为此目的, Clozure CL 提供了函数 OBJC:LOAD-FRAMEWORK

(OBJC:LOAD-FRAMEWORK framework-name interface-dir)

framework-name 是一个命名框架的字符串(例如, "Foundation", 或 "Cocoa"), interface-dir 是一个关键字, 用来命名和被命名框架相关联的接口数据库集合(例如, :foundation, 或 :cocoa)

假设被命名框架的接口数据库已经存在于标准搜索路径中, OBJC:LOAD-FRAMEWORK 经过搜索 OSX 的标准框架搜索路径找到并初始化框架束(bundle). 加载被命名框架可能新建 Objective-C 类和方法, 增长外部类型描述和入口点, 而且调整 Clozure CL 的分发函数.

若是你想使用的一个框架的接口数据库不存在, 你须要新建它们. 关于新建接口数据库的更多信息, 请参考 "13.5.2. Creating new interface directories"

14.8 如何把 Objective-C 名字映射到 Lisp 符号上 How Objective-C Names are Mapped to Lisp Symbols

对于 Cocoa 类, 消息等有一个标准的命名约定集. 只要遵循这个约定, 桥就能相对好地在 Objective-C 和 Lisp 名字之间自动转换.

例如, "NSOpenGLView" 变成 ns:ns-opengl-view; "NSURLHandleClient" 变成 ns:ns-url-handle-client; and "nextEventMatchingMask:untilDate:inMode:dequeue:" 变成 (:next-event-matching-mask :until-date :in-mode :dequeue). 多么拗口啊.

若是想了解一个给定的 Objective-C 或者 Lisp 名字如何被桥转换, 你可使用下面的函数:

(ccl::objc-to-lisp-classname string)
(ccl::lisp-to-objc-classname symbol)
(ccl::objc-to-lisp-message string)
(ccl::lisp-to-objc-message string)
(ccl::objc-to-lisp-init string)
(ccl::lisp-to-objc-init keyword-list)

固然了, 对于任何命名约定总会出现例外. 若是你遇到任何看起来像是缺陷的转换问题, 请经过邮件列表告诉咱们. 不然, 桥提供了两种处理例外的方法:

首先, 你能够把一个字符串当作 MAKE-OBJC-INSTANCE 的类名和 SEND 的消息来传递. 这些字符串将会被当作 Objective-C 名字不作转换直接解释. 对于一次性的例外来讲这种方法颇有用. 例如:

(ccl::make-objc-instance "WiErDclass")
(ccl::send o "WiErDmEsSaGe:WithARG:" x y)

其次, 你能够为你的例外定义一条特别的转换规则. 这对于你须要在代码中屡次用到的例外名字颇有用. 例如

(ccl::define-classname-translation "WiErDclass" wierd-class)
(ccl::define-message-translation "WiErDmEsSaGe:WithARG:" (:weird-message :with-arg))
(ccl::define-init-translation "WiErDiNiT:WITHOPTION:" (:weird-init :option))

Objective-C 命名的通常规则是名字中每一个有意义的单词以大写字母开头(除了第一个单词). 若是按照字面意思使用这条规则, "NSWindow" 将会被错误地转换为 N-S-WINDOW. 在 Objectve-C 中 "NS" 是一个特殊的单词, 不该该被拆分红单个的大写字母. 就像 "URL", "PDF", "OpenGL" 等同样. 在 Cocoa 中使用的大多数常见的特殊单词已经在桥中定义好了, 可是你能够按照下述方式定义一些新的:

(ccl::define-special-objc-word "QuickDraw")

注意在 SEND 中的像 (SEND V :MOUSE P :IN-RECT R) 之类的消息关键字可能看起来像一个 Lisp 函数调用中的关键字参数, 但它们实际不是. 全部关键字必须出现而且顺序明显. 不管 (:IN-RECT :MOUSE) 仍是 (:MOUSE) 都不会转换成 "mouse:inRect:"

另外, 做为一个特殊例外, 一个 "init" 前缀在 initializer 关键字中是可选的, 所以 (MAKE-OBJC-INSTANCE 'NS-NUMBER :INIT-WITH-FLOAT 2.7) 也能被表述成 (MAKE-OBJC-INSTANCE 'NS-NUMBER :WITH-FLOAT 2.7)

相关文章
相关标签/搜索