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)

相關文章
相關標籤/搜索