WebObjects/Web 应用/开发/Cocoa EO 应用
注意:这些技术依赖于已弃用的技术。使用需自担风险。-- Mike Schrag
CocoaEOApplication(在 WO 5.2 中正式称为 Cocoa 企业对象应用程序,但此处简称为 CEO)是一组对象,允许您在 Cocoa 中使用企业对象框架 (EOF)。即使这项技术尚未得到 Apple 的支持,它也自 NeXT 独立以来就一直存在。
在其早期版本中,CEO 使用 Objectve-C 作为其主要语言(现在是 Cocoa 的主要语言),但自 WO 5.0 以来,使用 EOF 的主要语言是 Java。为了在 Cocoa 中使用 EOF(特别是为了在 OSX 中使用 EOModeler),Apple 开发了所谓的 Java Bridge(例如,参见http://cocoadevcentral.com/articles/000024.php --- 有一篇名为“使用 Java Bridge”的文档,但在撰写本文时(2003 年 7 月 24 日),它在 developer.apple.com 上已不再可用;但是您可能会在硬盘上的 /Developer/Documentation/JavaBridge/JavaBridge.pdf 中找到一份副本)。为了开发 CEO,您必须习惯其机制。
更新(2005 年 6 月 3 日):JavaBridge.pdf 现在位于http://developer.apple.com/documentation/Cocoa/Conceptual/Legacy/JavaBridge/JavaBridge.pdf
在创建 CEO 项目时,有一些事项需要注意(此注释适用于 WO 5.2,并已针对 5.2.2 进行修订,现在适用于 WO 5.2.4)
当您启动 Project Builder 的向导并请求 Cocoa/EO 应用程序时,它将创建一个不可用的 MainMenu.nib 文件。要解决此问题:关闭项目;删除“损坏”的文件 MainMenu.nib(它位于项目的 English.lproj 文件夹中);启动 Interface Builder 并创建一个空界面;将其另存为 MainMenu.nib(最好使用扩展名)到“损坏”文件所在的位置;重新启动 Project Builder;完成。
- .... 在 OSX 10.3(Panther+Xcode)的 WO 5.2.2 中,已修复了之前的错误。
- .... 在 OSX 10.4(Tiger+Xcode)的 WO 5.2.4 中,它仍然有效。
第一次从 EOModeler 将实体拖到 Interface Builder 中时,将为您创建一个 EOEditingContext(如果将实体拖到窗口上,还将创建一个 NSTableView 和一个 EODisplayGroup;如果拖到类查看器上,则只会创建 EODisplayGroup)。默认情况下(并非总是理想)是在加载时获取实体中的所有对象;如果您不希望这样做,请转到检查器(选择 EODisplayGroup),然后取消选中相应的按钮。
基本上,您已经准备就绪。您可以使用显示组和编辑上下文的所有操作来查看和操作您的数据库,而无需编写任何代码 --- 事实上,您既不必编译项目;只需连接您的按钮并从文件菜单运行“测试界面”命令(或从键盘使用 cmd+r),您就会看到您的数据库显示出来。
当然,如果您是一位经验丰富的程序员并希望向您的应用程序添加一些“逻辑”,您可以这样做。您需要做的第一件事是选择一种语言;我更喜欢 Obj-C,但也可以使用 Java... 事实上,即使您决定不使用它,它也会被使用。EOF 是用纯 Java 编写的,因此,即使您没有用 Java 编写一行代码(有时这似乎是不可避免的),您也会通过 Java Bridge 使用它。我稍后会详细介绍...
根据一位虚拟朋友(Arturo Perez)的建议,我决定添加一个简单的指南来构建 CEO。
这真的很简单... 几乎和 D2W 一样简单,但更美观。
有模型吗?如果有,您可以操作您的数据库,而无需编写任何代码。尝试以下操作
- 创建一个 Cocoa/EO 项目并导入您的模型(如果您仍在使用 5.2.1,请注意重新创建 nib 文件)
- 打开您的 MainMenu.nib(来自 PB 或 Xcode)并将一个新窗口从 IB 的调色板拖到其中
- 打开您的模型并将一个实体拖到窗口中
- 在 IB 中,按 cmd+r
您的表格将显示出来!!
如果您熟悉 IB,剩下的就是历史了,但如果您不熟悉,可以尝试
- 将一个按钮从调色板拖到您的窗口中
- 将其(使用 cntrl+拖动)连接到您的显示组(在步骤 3 中创建)并通过双击选择插座“插入”
- 按 cmd+r
- 按下按钮
您将在表格中看到一个新行!!
您想添加一些自定义逻辑吗?
9. 选择“类”选项卡,选择您的根类(我更喜欢 NSObject)并按 Enter 10. 按 cmd+1,添加一个插座(displayGroup)和一个动作(doit:) 11. 按 cmd+opt+f(对面板回答“确定”)并按 cmd+opt+i 12. 连接您的插座,添加一个按钮并将其连接到您的动作。保存。13. 在 PB(或 Xcode)中,您将看到您的文件。编辑它们以实现 doit:。例如,尝试以下内容
- (IBAction)doit:(id)sender{ [[displayGroup displayedObjects] takeValueForKey:aValue :@"aKey"]; }
- 在 PB(或 Xcode)中按 cmd+r
您将编译并运行您的新应用程序。表格显示后,按下(第二个)按钮。
就是这样!!您现在可以准备开发市场上最好的应用程序了 ;^()
如果您知道您将需要大量的内存,或者在控制台中收到“java.lang.outOfMemoryError”消息,或者您的应用程序根本没有执行其应执行的操作,您可能需要阅读本节... 否则,只需跳过它。
您还在吗?好吧,您的问题可能是 VJM 没有为您分配足够的内存。
在 Project Builder 中,在目标下并选择主目标,转到 Info.plist 条目 > 纯 Java 特定(在 Xcode 中,您必须单击目标组的小三角形,双击主目标)。在“附加 VM 选项”文本字段中添加类似以下内容
-Xms256m -Xmx256m
(这将为您的 JVM 提供 256MB 的内存,而不是默认的 64MB。)
此外,习惯正确使用 NSAutoreleasePool 也很方便(请参阅其文档和相关主题;特别是阅读“Objective-C 编程语言”一书的第 4 章http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC)。Java Bridge 在 Java 端很好地使用了“垃圾回收”,在 Obj-C 端使用了“引用计数”;不用担心。
正如我所说,即使您的代码仅使用 Obj-C,您也将在通过 Bridge 处理 Java 对象;因此,您必须知道如何在 Java 端操作这些“分配”的对象。
从 Obj-C 端在 Java 端分配对象的最简单方法是,例如,
id aJavaObject = [NSClassFromString(@"java.lang.Object") new];
不要忘记释放此类对象;因为您分配了它,所以您拥有它!
对于来自 com.apple.cocoa.foundation.* 的那些对象,您实际上不必关心(除了 NSEnumerator 之外);它们来回传递都很正常。甚至一些简单的对象,如 String(映射到 NSString)、Number(映射到 NSNumber)以及一些基本类型,如 int、char、boolean 等。
到目前为止,一切顺利。但是,当您想要枚举数组时要小心。一方面,如果数组是在 Obj-C 端创建的,并且您请求一个 objectEnumerator,您将收到一个 NSEnumerator。此对象实现 nextObject 方法来遍历数组的元素。另一方面,如果数组是在 Java 端创建的(例如,作为 allObjects() 调用的结果),并且您请求一个 objectEnumerator,您将收到一个实现 nextElement() 方法的 Enumerator 对象。
以下是一些这两种情况的示例。
一个 Obj-C 数组
NSArray* anObjCArray = [NSArray arrayWithObjects:objA, objB, objC, objD, nil]; NSEnumerator* en = [anObjCArray objectEnumerator]; id o = nil; while(o = [en nextObject]){ // here your code using o, // which will traverse all objects in the array }
一个 Java 数组
NSArray* aJavaArray = [someDisplayGroup displayedObjects]; NSEnumerator* en = [aJavaArray objectEnumerator]; while([en hasMoreObjects]){ id o = [en nextElement]; // here your code using o, which will traverse all objects in the array }
这些简单的示例展示了在开发 CEO 时需要牢记的一些事项
- Obj-C 的方法可以接受任意长度的对象列表作为参数(通常以 nil 结尾);Java 的方法则不行。如果你知道你的方法将由 Java 对象处理,**千万不要**使用这种结构……它根本不会被识别。相反,Java 使用以下形式的数组。
new Object[] {objA,objB,objC,objD}
因此,你必须**首先**创建数组,并在分配后将其用作参数。一个重要的例子是类方法 qualifierWithQualifierFromat(String format, NSArray arguments),它由 com.webobjects.eocontrol.EOQualifier 实现。我稍后会再讨论这个问题……
- NSEnumerator 以一个 nil 对象结束其“路径”;如果 Java 的 Enumerator 被请求获取更多元素,则会抛出异常。因此,为了避免这种异常,你必须使用 hasMoreElements 方法。
- Java 端的对象从 Obj-C 端调用,就像它们是 Obj-C 对象一样(使用相同的语法);也就是说,如果 Java 对象 anObject 实现了方法 someMethod(Object someParameter, Object someOther),则必须使用以下形式的代码调用它:
[anObject someMethod:someParameter :someOther];
也许 CEO 集成中最糟糕的部分是数组的管理;有 5 种类型的数组你必须注意
- NSArray;如 Foundation.h 中定义(Obj-C 端)。
- NSArray;如 com.apple.cocoa.foundation 中定义(Java 端)。
- NSArray;如 com.webobjects.foundation 中定义(Java 端)。
- 对象的 C 数组(如 id array[])。
- 对象的 Java 数组(如 Object[])。
这太多了!但是生活就是生活,最好适应它们……
通常,当你不需要在 Cocoa 中使用 EOF 时,你可以忘记 com.webobjects.foundation 中的数组;桥接器默认情况下会将 Foundation.h 中的数组转换为 com.apple.cocoa.foundation 中的数组,反之亦然,没有任何问题……好吧,几乎没有问题:从 Java 到 Obj-C,每个数组都作为 NSCFArray 传递,该数组**没有**文档,但它可以像 NSArray 一样工作。另一方面,从 Obj-C 到 Java,它们作为 com.apple.cocoa.NSMutableArray 传递,它继承自普通的 NSArray——尝试在它们通过桥接器传递后,在两端打印它们的类描述,使用以下代码
[textField setStringValue:[[aJavaArray class] description]];
或者
textField.setStringValue(anObjCArray.getClass().toString());
这并不像看起来那么糟糕。在实践中,你不需要了解这一点。
真正的问题出现在你想要使用 EOF 时。大多数企业对象(在 com.webobjects.* 中的某个地方定义),当使用数组时,它们都在等待 com.webobjects.foundation.NSArray,而不是上述任何一种。
这个问题有很多解决方案。第一个(对于那些了解 Java 和 Obj-C 的人)是在负责根据需要分配这些数组的 bridgeTool.java 对象中。
public com.webobjects.foundation.NSArray arrayFromArray(com.apple.cocoa.foundation.NSArray cocoaArray) { com.webobjects.foundation.NSMutableArray woArray = new com.webobjects.foundation.NSMutableArray(); int i; for(i=0;i<cocoaArray.count();++i){ woArray.addObject(cocoaArray.objectAtIndex(i)); } return woArray; }
并使用类似以下内容从你的 Obj-C 对象中调用此方法:
NSArray* cocoaArray; // suppose this exists id bridgeTool = [[NSClassFromString(@"bridgeTool") new] autorelease]; id woArray = [bridgeTool arrayFromArray:cocoaArray];
获得 woArray 后,你可以将其用作企业对象调用的参数。
我还没有找到调用 Java 方法的方法,该方法的参数是一个 Object[] 数组。如果有人阅读本文并发现了方法,请告知我们……
在使用 EOF 时,在某些情况下,你可能希望构建一个限定符来执行获取操作。限定符是 com.webobjects.eocontrol.EOQualifier 的一个实例。如果你的限定符不包含任何日期,则它就像这样简单:
NSString* partOfAName; // suppose this exists NSString* qualifierFormat = [NSString stringWithFormat:@"name caseInsensitiveLike '*%@*'", partOfAName]; id qualifier = [NSClassFromString(@"com.webobjects.eocontrol.EOQualifier") qualifierWithQualifierFormat:qualifierFormat :nil];
然后在获取规范中使用它:
id fetchSpecification = [[NSClassFromString(@"com.webobjects.eocontrol.EOFetchSpecification") new] autorelease]; [fetchSpecification setEntity:@"myEntity"]; [fetchSpecification setQualifier:qualifier];
另一方面,如果你想在限定符中使用日期,由于 NSCalendarDate 没有正确转换为 NSTimestamp,你必须再次使用 bridgeTool.java 的技巧
public NSTimestamp nsTimestampFromString(String dateString){ NSTimestampFormatter formatter = new NSTimestampFormatter("%d %m %Y"); ParsePosition pp = new ParsePosition(0); NSTimestamp myNSTimestamp = (NSTimestamp)formatter.parseObject(dateString, pp); return myNSTimestamp; }
并通过以下方式从 Obj-C 调用它:
NSCalendarDate aCalendarDate; // suppose this exists [aCalendarDate setCalendarFormat:@"%d %m %Y"]; id nsTimestamp = [bridgeTool nsTimestampFromString:[aCalendarDate description]];
(只需保持你的格式一致)。
也可以(至少在 Tiger 上运行的 WO 5.2.4 中)使用 com.webobjects.eointerface.cocoa.EOCocoaUtilities,如下所示:
id objPath = @"com.webobjects.eointerface.cocoa.EOCocoaUtilities"; id aDate = [NSCalendarDate date]; id nsTimestamp = [NSClassFromString(objPath) timestampForGregorianDate:aDate];
从这里,你可以将 nsTimestamp 用作 com.webobjects.* 中定义的那些类(例如,在你的限定符中)的参数。
让我们从本节开始描述如何将 OS X 的两个功能最强大的框架整合在一起;即 EOF 和 NSDocument 框架(本节写于 2005 年 7 月,当时正在使用 Tiger 上运行的 WO 5.2.4)——稍后,我们将把它们与 Core Data 集成,我认为在不久的将来,Core Data 结合 Bindings 将取代 EOF。
让我们以“教程”的方式进行。
- 启动 Xcode(我正在使用 2.0)并选择“文件”>“新建项目”。在下一个面板中选择“Cocoa-Java 基于文档的应用程序”。这将生成一个项目,该项目已准备好使用 Java Bridge 并实现了 NSDocument 架构——查看生成的 文件和目标;特别是,双击主目标并检查显示的所有详细信息。
- 在项目窗口中,选择“框架”文件夹。从“项目”菜单中,选择“添加到项目”(cmd+alt+a)。在浏览器中,转到“系统”>“库”>“框架”并选择 JavaEOCocoa.framework;点击“确定”。
- 再次按下 cmd+alt+a,浏览到“系统”>“库”>“框架”,然后到 JavaEOAccess.framework>“资源”>“Java”并选择 javaeoaccess.jar;点击“确定”。
- 对以下文件重复上一步:javaeocontrol.jar、javaeointerface.jar、javaeointerfacecocoa.jar、javafoundation.jar 和 javaxml.jar。
- 添加你的模型——仔细检查其文件夹是否以蓝色显示(而不是黄色)。
基本上你已经完成了。但是,如果你——像我一样——更喜欢在 Objective-C 中工作,请执行以下操作
- 通过双击 MyDocument.nib 文件启动 Interface Builder。
- 点击“文件所有者”,然后点击“类”选项卡。你会注意到所选类是 MyDocument,它继承自 NSDocument。选择 NSDocument 并按“回车”创建新的子类;为其命名(例如,MyCDocument)。然后从“类”菜单中选择“创建文件……”并接受。
- 返回到“实例”选项卡,选择“文件所有者”,并在“自定义类”检查器(cmd+5)中选择新创建的类。保存并隐藏。
- 双击项目的“主目标”。选择“文档类型”并根据新类进行相应的编辑(例如,将 MyDocument 更改为 MyCDocument)。
- 在三步下方创建的 .m 文件中,添加以下内容:
- (NSString *)windowNibName { return @"MyDocument"; }
编译并运行(cmd+r)。
最后,如果你愿意,你可以消除向导创建的 .java 文件中关于已弃用方法的警告
- 从项目窗口中删除 MyDocument.java 文件。
- 在主目标的“Cocoa Java 特定”窗格中选中“需要 Java”按钮——在三步下方打开的窗口中。
编译并运行(cmd+r)。
至此,你已经创建了一个多文档 CEO……
-- StrauszRicardo