Clozure CL中文版022:Objective-C桥
Category:帮助手册Objective-C桥
- 1.2的变化
- 使用Objective-C类
- 实例化Objective-C对象
- 调用Objective-C方法
- 定义Objective-C类
- 定义Objective-C方法
- 加载框架
- Objective-C名称如何映射到Lisp符号
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上开发GUI应用程序和IDE OS X(以及支持Objective-C的任何平台,例如GNUStep)。最终的目标,比以往更接近,是将Cocoa完全集成到CLOS中。
当前版本为基本的Objective-C操作提供了类似Lisp的语法和命名约定,并在编译时检查自动类型处理和消息的有效性。它还为使用Cocoa提供了一些便利设施。
1.2的变化
Clozure CL 1.2版导出了本章所述的大多数有用符号; 在以前的版本中,大多数都是CCL包中的私有。
有几个新的读取器宏可以比以前更方便地引用与Objective-C桥接器一起使用的几类符号。有关这些读取器宏的完整说明,请参阅外部函数接口字典,特别是开头的条目,描述读取器宏。
与以前的版本一样,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包中导出的名称(在接口文件中声明的预定义类的情况下),也可以使用从Lisp中定义类的DEFCLASS形式(with :METACLASSNS:+NS-OBJECT)中提供的名称。类的Lisp的名称现在宣告是一个“静态”变量(仿佛由DEFSTATIC作为中所述 “静态变量”部分)和给定的类对象作为其值。换一种说法:
(send (find-class ‘ns:ns-application) ‘shared-application)
和
(send ns:ns-application ‘shared-application)
是等价的。(由于绑定“静态”变量是不合法的,因此可能需要重命名某些内容,以便名称与Objective-C类名称不一致的不相关变量不会这样做。)
使用Objective-C类
大多数标准CLOS类的类名为STANDARD-CLASS。在Objective-C对象模型中,每个类都是(通常是唯一的)元类的实例,它本身是“基础”元类的实例(通常是名为“NSObject”的类的元类。)因此,目标 – 名为“NSWindow”的C类和Objective-C类“NSArray”是其独特元类的(唯一)实例,其名称分别也是“NSWindow”和“NSArray”。(在Objective-C世界中,专门化类行为(如实例分配)更为常见和有用。)
当Clozure CL首次加载包含Objective-C类的外部库时,它会识别它们包含的类。外部类名称(例如“NSWindow”)通过桥的转换规则(例如NS:NS-WINDOW)映射到“NS”包中的外部符号。类元组名称发生类似的转换,前面加上“+”,产生类似NS:+ NS-WINDOW的东西。
这些类集成到CLOS中,使得元类是OBJC类的实例:OBJC-METACLASS,类是元类的实例。为每个实例变量创建了SLOT-DESCRIPTION元对象,并且类和元类经历了与“标准”CLOS类初始化协议非常相似的事情(不同之处在于已经分配了这些类)。
当你(需要“COCOA”)执行所有这些初始化时,当前需要几秒钟; 它可以想象加速一些,但它永远不会很快。
当该过程完成时,CLOS会知道几百个新的Objective-C类及其元类。Clozure CL的运行时系统可以将Objective-C类的MACPTR可靠地识别为CLASS对象,并且可以(相当可靠但启发式地)识别这些类的实例(尽管这里有复杂的因素;请参阅下文。)SLOT-VALUE可用于在Objective-C实例中访问(并且,小心,设置)实例变量。要看到这个,请执行:
? (require “COCOA”)
并且,在等待一段Cocoa侦听器窗口出现之后,激活该Cocoa侦听器并执行:
? (describe (ccl::send ccl::*NSApp* ‘key-window))
这会发送一条消息,询问关键窗口,该窗口是具有输入焦点的窗口(通常是最前面的),然后对其进行描述。我们可以看到,NS:NS-WINDOWs有很多有趣的插槽。
实例化Objective-C对象
创建一个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)>
如果你在Objective C中写作,那么值得看看如何做到这一点:
[[NSNumber alloc] initWithInt: 42]
分配Objective-C类的实例涉及向类发送“alloc”消息,然后使用那些initargs 别对应于插槽initargs作为要发送到新分配的实例的“init”消息。所以,上面的例子可以更详细地完成:
? (defvar *n* (ccl::send (find-class ‘ns:ns-number) ‘alloc))
*N*
? (setq *n* (ccl::send *n* :init-with-int 42))
#<NS-CF-NUMBER 42 (#x16D340)>
那个setq很重要; 这是init决定替换对象并返回新对象而不是修改现有对象的情况。实际上,如果你省略了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: 0x1fb520> (#x1FB520)>
此示例调用(make-instance)而没有initargs。执行此操作时,仅分配对象,而不是初始化对象。然后它发送“init”消息以手动进行初始化。
还有一个用于实例化Objective-C类的替代API。您可以调用OBJC:MAKE-OBJC-INSTANCE,将Objective-C类的名称作为字符串传递给它。在以前的版本中,OBJC:MAKE-OBJC-INSTANCE可能比OBJC:MAKE-INSTANCE类没有定义任何Lisp插槽的情况更有效; 这已不再是这种情况。您现在可以OBJC:MAKE-OBJC-INSTANCE视为完全等效于OBJC:MAKE-INSTANCE,除了您可以为类名传递字符串,这在类名以某种方式异常的情况下可能很方便。
调用Objective-C方法
在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-super)而不是(send)。它与CLOS(call-next-method)的目的大致相同; 当你使用(send-super)时,消息由超类处理。当它被子类中的方法遮蔽时,可以使用它来获取方法的原始实现。
为Objective-C方法调用键入强制
Clozure CL的FFI处理Lisp和外部数据之间的许多常见转换,例如取消装箱浮点算法和装箱浮点结果。该网桥增加了一些自动转换:
对于需要指针的任何消息参数,NIL等效于(%NULL-PTR)。
对于任何布尔参数,T / NIL相当于#$ YES /#$ NO。
返回BOOL的任何方法返回的#$ YES /#$ NO将自动转换为T / NIL。
返回结构的方法
一些Cocoa方法返回小结构,例如用于表示点,范围,大小和范围的结构。在Objective C中编写时,编译器会隐藏实现细节。不幸的是,在Lisp中我们必须稍微了解它们。
返回结构的方法以特殊方式调用; 调用者为结果分配空间,并将指针传递给它作为方法的额外参数。这称为结构返回,或STRET。别看我; 我没说这些东西。
这是在Objective C中对此的简单使用。第一行将“bounds”消息发送到v1,后者返回一个矩形。第二行将“setBounds”消息发送到v2,将相同的矩形作为参数传递。
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不需要做任何特殊的操作,因为将结构作为参数传递并不复杂。
为了使STRET更易于使用,桥接器提供了两种便利。
首先,您可以使用宏slet 并slet*在一个步骤中将局部变量分配和初始化为外部结构。上面的例子可以更简洁地写成:
(slet ((r (send v1 ‘bounds))) (send v2 :set-bounds r))
其次,当一个调用send是在另一个调用内部时,内部调用是slet在它周围隐含的。所以,实际上可以写一个:
(send v1 :set-bounds (send v2 ‘bounds))
为方便起见,Objective-C编译器还提供了几个伪函数,用于创建特定类型的对象。目前桥梁支持以下内容:NS-MAKE-POINT,NS-MAKE-RANGE,NS-MAKE-RECT和NS-MAKE-SIZE。
这些伪函数可以在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和NS-WIDTH。
请注意,方法中还有一个send-super/stret 用途。就像send-super,它忽略了子类中的任何阴影方法,并调用属于其超类的方法的版本。
可变Arity消息
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运行时系统不提供有关哪些消息是变量arity的任何信息,所以必须显式声明它们。Cocoa中的标准变量arity消息由网桥预先声明。如果需要声明一个新的变量arity消息,请使用(DEFINE-VARIABLE-ARITY-MESSAGE“myVariableArityMessage:”)。
优化
当有足够的信息时,桥接器很难优化消息发送。它有两种情况。在任何一个中,消息发送应该与在Objective C中写入时一样有效。
第一种情况是在编译时知道消息和接收者的类。一般来说,接收者类知道的唯一方法就是声明它,你可以使用DECLARE或THE表单。例如:
(send (the ns:ns-window w) ‘center)
请注意,Objective-C中没有办法命名类的类。因此,桥梁提供了一个声明@METACLASS。“NSColor”实例的类型是ns:ns-color。的类型类 “NSColor”是(@metlass ns:ns-color):
(let ((c (find-class ‘ns:ns-color))) (declare ((ccl::@metaclass ns:ns-color) c)) (send c ‘white-color))
允许优化的另一种情况是在编译时只知道消息,但其类型签名是唯一的。在Cocoa目前提供的超过6000条消息中,只有大约50条消息具有非唯一类型签名。
具有不唯一的类型签名的消息的示例是SET。它为NSColor返回VOID,但为NSSet返回ID。为了优化具有非唯一类型签名的消息发送,必须在编译时声明接收器的类。
如果类型签名不是唯一的或者在编译时消息未知,则必须使用较慢的运行时调用。
当接收者的类未知时,网桥的优化能力依赖于它维护的类型签名表。首次加载时,桥接器会通过扫描每个Objective-C类的每个方法来初始化此表。稍后定义新方法时,必须更新表。当您在Lisp中定义方法时会自动发生这种情况。在进行任何其他重大更改(例如加载外部框架)之后,您应该重建表:
? (update-type-signatures)
因为send它们的亲戚 send-super,send/stret和send-super/stret是宏,它们不能作为函数的参数进行funcall编辑,apply编辑或传递。
要解决这个问题,有功能等同于他们:%send,%send-super,%send/stret,和 %send-super/stret。但是,只有在宏不能执行时才应使用这些函数,因为它们无法进行优化。
定义Objective-C类
您可以定义自己的外部类,然后可以将其传递给外部函数; 您在Lisp中实现的方法将作为回调提供给外部代码。
你也可以定义现有类的子类,在Lisp中实现你的子类,即使父类在Objective C中。一个这样的子类是CCL :: NS-LISP-STRING。制作NS-WINDOW-CONTROLLER的子类也特别有用。
我们可以使用MOP来定义新的Objective-C类,但是我们必须做一些有趣的事情:我们想要在DEFCLASS选项中使用的METACLASS通常在我们创建类之前不存在(回想一下,为了参数,Objective-C类有唯一的和私有的元类。)我们可以通过指定一个已知的Objective-C元类对象名作为DEFCLASS:METACLASS对象的值来解决这个问题。根类NS的元类:NS-OBJECT,NS:+ NS-OBJECT,是一个不错的选择。要创建NS的子类:NS-WINDOW(为简单起见,没有定义任何新的插槽),我们可以这样做:
(defclass example-window (ns:ns-window) () (:metaclass ns:+ns-object))
这将创建一个名为EXAMPLE-WINDOW的新Objective-C类,其元类是名为+ EXAMPLE-WINDOW的类。该类将是OBJC类型的对象:OBJC-CLASS,并且元类将是OBJC类型:OBJC-METACLASS。EXAMPLE-WINDOW将是NS-WINDOW的子类。
使用外部插槽定义类
如果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。
使用Lisp插槽定义类
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))
正如人们所料,这具有内存管理的含义:只要Objective-C实例存在,我们就必须维护MACPTR和一组lisp对象(其插槽)之间的关联,并且我们必须确保目标 – C实例存在(没有调用它的-dealloc方法),而lisp试图将它视为一个无法“解除分配”的第一类对象,而它仍然可以引用它。将一个或多个lisp对象与外部实例相关联通常非常有用; 如果你“手工”这样做,你将不得不面对许多相同的内存管理问题。
定义Objective-C方法
在Objective-C中,与CLOS不同,每个方法都属于某个特定的类。这可能不是一个奇怪的概念,因为C ++和Java做同样的事情。当您使用Lisp定义Objective-C方法时,只能定义属于已在Lisp中定义的Objective-C类的方法。
您可以使用两个不同的宏中的任何一个来定义Objective-C类的方法。define-objc-method 接受包含消息选择器名称和类名称以及正文的双元素列表。objc:defmethod 表面上类似于普通的CLOS defmethod,但是在Objective-C类上创建方法,其限制与创建的方法相同define-objc-method。
使用define-objc-method
如Calling Objective-C方法一节所述,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。它有两个插槽: 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访问器获取窗口字段的值。
这是一个带参数的例子。请注意,没有参数的方法的名称是普通符号,但是使用参数,它是一个关键字:
(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方法”。我们的其他方法所说的: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)))))
这个方法将takeAction: 在Objective-C中调用。将要用作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)
使用objc:defmethod
宏OBJC:DEFMETHOD可用于定义Objective-C方法。它看起来CL:DEFMETHOD在某些方面表面上看起来很像。
它的语法是
(OBC:DEFMETHOD name-and-result-type ((receiver-arg-and-class) &rest other-args) &body body)
name-and-result-type对于返回值的类型的方法:ID,或者包含Objective-C消息名称的列表和具有不同外部结果类型的方法的外部类型说明符,可以是Objective-C消息名称。
receiver-arg-and-class是一个双元素列表,其第一个元素是变量名,第二个元素是Objective-C类或元类的Lisp名称。接收方变量名称可以是任何可绑定的lisp变量名称,但SELF可能是合理的选择。接收器变量被声明为“不可设置”; 即,尝试在方法定义的主体中更改接收器的值是错误的。
other-args是变量名(表示类型的参数:ID)或2元素列表,其第一个元素是变量名,第二个元素是外部类型说明符。
考虑这个例子:
(objc:defmethod (#/characterAtIndex: :unichar) ((self hemlock-buffer-string) (index :<NSUI>nteger)) …)
characterAtIndex:当HEMLOCK-BUFFER-STRING使用类型的附加参数在类的对象上调用时,该方法:<NSU>integer返回type 的值 :unichar。
该风作为比其他一些指针类型参数:ID(例如指针,通过值传递的记录)被表示为输入外国指针,从而使更高级别,类型检查访问器可以在类型的参数被使用:ns-rect,:ns-point,等。
在通过定义的方法体中,定义OBJC:DEFMETHOD了本地函数 CL:CALL-NEXT-METHOD。它不像CL:CALL-NEXT-METHOD在CLOS方法中使用时那样通用,但它具有一些相同的语义。它接受包含方法other-args列表中存在的参数,并调用包含方法的版本,该方法将使用接收器和其他提供的参数在接收者类的超类的实例上调用。(将当前方法的参数传递给下一个方法的习惯用法很常见,如果它没有接收到参数,那么CALL-NEXT-METHODinOBJC:DEFMETHODs应该这样做。)
通过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会发出可持续错误信号。
方法重新定义约束
目标C没有像Lisp那样设计,考虑到运行时重新定义。因此,关于如何以及何时可以替换Objective C方法的定义存在一些限制。目前,如果你打破这些规则,任何事情都不会崩溃,但这种行为会令人困惑; 所以不要。
Objective C方法可以在运行时重新定义,但它们的签名不应该更改。也就是说,参数的类型和返回类型必须保持不变。这样做的原因是更改签名会更改用于调用方法的选择器。
如果已在一个类中定义了一个方法,并且您在子类中定义了该方法,则为原始方法设置阴影,它们必须具有相同的类型签名。但是,没有这样的约束,如果两个类没有关联,并且方法碰巧具有相同的名称。
加载框架
在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则通过搜索OS X的标准框架搜索路径来查找并初始化框架包。加载命名框架可以创建新的Objective-C类和方法,添加外部类型描述和入口点,以及调整Clozure CL的调度功能。
如果您要使用的框架不存在接口数据库,则需要创建它们。有关创建接口数据库的更多信息,请参阅创建新接口目录。
Objective-C名称如何映射到Lisp符号
Cocoa类,消息等有一组标准的命名约定。只要遵循它们,该桥就可以很好地自动转换Objective-C和Lisp名称。
例如,“NSOpenGLView”变为ns:ns-opengl-view; “NSURLHandleClient”变为ns:ns-url-handle-client; 和“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”将被翻译为NS-WINDOW,这似乎是错误的。“NS”是Objective-C中的一个特殊单词,不应在每个大写字母处打破。同样“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”前缀是可选的,因此(MAKE-OBJC-INSTANCE’NS-NUMBER:INIT-WITH-FLOAT 2.7)也可以表示为(MAKE-OBJC-INSTANCE’ NS-NUMBER:WITH-FLOAT 2.7)
http://mip.i3geek.com