为移动 web 应用程序开发而探索 Cappuccino

IBMdW 发布于 2011/11/07 19:08
阅读 647
收藏 1

简介: 多年来,web 应用程序已经取代了桌面应用程序的地位,尽管它们的质量通常比桌面应用程序低。这种差别的部分原因是,在浏览器中运行时,桌面应用程序的功能相对而言比 web 应用程序更强大。但是,随着近来现代浏览器及其 HTML5 规范实现的发展,二者之间的特性和性能差异迅速缩小了。另一个主要原因是,与桌面开发人员相比,web 开发人员必须处理级别低得多的 API。Cappuccino 将 Cocoa 框架引入 web 开发,完全改变了这种局面,这使得它成为对移动 web 开发人员极具吸引力的选择。请您自己确定 Cappuccino 能否很好地适应您的下一个卓越的移动 web 应用程序。

关于本系列

移动应用程序的开发发展迅速,许多开发人员选择走移动 web 路线,不再为每个不同的移动平台重复编写相同的应用程序 。然而,由于您 “追求 web”,您需要放弃本地移动应用程序开发人员曾经和容易构建的应用程序框架。结果是出现了几个 web 应用程序框架。在本系列的 4 篇文章中,我们将看到其中 4 个框架:SproutCore、Cappuccino、jQTouch 和 Sencha Touch。我们将对它们进行比较,评估使用它们构建一个移动 web 应用程序的优势和劣势。

先决条件

在本文中,我们将探索 Cappuccino 框架,以及使用它创建的一个典型应用程序。Cappuccino 引入了自己的编程语言 Objective-J,它非常类似与 Objective-C。因此,预先了解 Objective-C(或者它起源的语言 C 和 Smalltalk)将有助于简化 Objective-J 学习过程。由于是纯客户端框架,Cappuccino 不需要服务器端组件。您的 Cappuccino 应用程序只需要一个基本 web 服务器。参见 参考资料 中这些工具的链接。

Cappuccino 概览:你好,Objective-J

如果您阅读了本系列第 1 部分,那么您可能会熟悉本节的部分信息。在前一篇文章中,我们谈到 SproutCore 框架如何 “引入一个由 Cocoa 激发的编程模型”。这个声明并不适用 Cappuccino,因为 Cappuccino 不是由 Cocoa 激发的框架,而是 web 上的 Cocoa。无论您是否熟悉 Cocoa,这都似乎有点荒谬。Cocoa 都是完全用 Objective-C 编写的一个桌面应用程序框架。另一方面,Cappuccino 是一个纯客户端 web 应用程序框架,Objective-C 不能在浏览器中使用。这就是 Objective-J 发挥作用的地方。

Objective-J 是由 Objective-C 激发的一种新编程语言。如果您熟悉 Objective-C,那么您将想起它实际上是一个 C 超集(superset)。类似地,Objective-J 是一个 JavaScript 超集。它将 Objective-C 在 C 上添加的大部分语法添加到 JavaScript。Objective-C 程序员有时不得不使用 C,但通常只是为了执行低级任务或使用一个 C 库。这种情况对 Objective-J 程序员同样适用。通过将 Objective-C 语法带到 JavaScript,从而带到浏览器,Cappuccino 使得将大部分 Cocoa 框架移植到 web 成为可能。为了更好地感受 Cappuccino 及其提供的好处,我们来看一个 Cappuccino 应用程序。

Cappuccino 应用程序

要进行一个 “苹果-苹果比较(apples-to-apples comparison)”,我们将使用本系列第 1 部分中开发的应用程序并使用 Cappuccino 重写它。 尽管我们不会试图使其看起来像原来的版本,但我们 会尝试复制该应用程序的所有功能。回想一下,该应用程序是一个公司的内部员工目录及其联系信息。对于我们的 SproutCore 实现,我们首先创建了一个数据模型,因为这是 SproutCore — 和 Cocoa 这样的 MVC 框架的关键部分。因此,我们的 Cappuccino 实现也将首先创建一个类似的数据模型。清单 1 展示了用 Objective-J 编写的员工数据模型。


清单 1. Objective-J 员工数据模型
@implementation Employee: CPObject
{
    CPString firstName @accessors;
    CPString lastName @accessors;
    CPString phone @accessors;
    CPString email @accessors;
}

- (CPString)fullName
{
    return [[CPString alloc] initWithFormat:@"%@ %@", firstName, lastName];
}

@end

您可能会注意到的第一件事是:清单 1 中的代码以 @implementation 关键字开始。在 Objective-C 中,每个类都有两个文件。第一个是头文件(.h),用于声明类接口;第二个是实现文件(.m),接口的所有方法都在其中实现。@implementation 用于声明正在实现的是哪个类。幸运的是,Objective-J 不使用头文件,只有一个实现文件。在 @implementation 关键字后面,我们看到了类名(Employee)及其超类。在 Objective-C 中,基对象是 NSObject;在 Cappuccino,基对象是 CPObject。换言之,每个类都是 CPObject。注意,Cappuccino 中的大部分核心类都以 CP(代表 Cappuccino)开头,并遵循 Cocoa 传统。

注意,在 Cocoa 中,核心类通常以 NS 前缀开头,NS 代表 NextStep,这是最初引入 Objective-C 的操作系统。

接下来,清单 1 中出现类的字段/属性。注意,每个变量的类型都首先声明。在这里,每个类型都是 CPString。然后是变量。最后是 @accessors 注释,这是一个宏,用于为它前面的字段创建 getter/setter 方法。清单 2 展示了为 firstName 字段生成的访问器代码。


清单 2. 访问器代码
- (CPString)firstName
{
    return firstName;
}

- (void)setFirstName:(CPString)aFirstName
{
    firstName = aFirstName;
}

可以看到,一个 @accessors 注释代替了 8 行代码。但是,请记住, 清单 2 中的代码是将为带有@accessors 注释的每个字段生成的确切代码。因此,要获取 firstName 的值,调用清单 2 中显示的第一个方法(firstName)。类似地,要设置值,调用第二个方法(setFirstName:)。与 Objective-C 的合成属性,也许还有一些 JavaScript 属性不同的是,这并不提供 “点访问”方式(例如,employee.firstNameemployee.firstName = someOtherName)。

回到清单 1。字段及其访问器声明之后,下面出现的是 Employee 类的方法。在这里,我们只有一个方法:fullName。 这个方法只返回 firstNamelastName 字段,中间使用一个空格连接。这个实现展示了 Objective-J 的几个关键部分。要创建一个新对象(这里是将返回的字符串),首先分配对象,然后初始化对象。alloc 方法是从 CPObject 继承的,但 initWithFormat:CPString 独有的。通常,不同的类拥有各种方法来初始化新实例。要调用这些方法,使用 [object method] 语法。方法调用可以视为传递到对象的消息 — 即,您指示它 alloc,并将结果告知 initWithFormat:。这是直接从 Objective-C 借用的。

关于清单 1 中的 fullName 方法,需要注意的最后一点是:它的前面有一个负号(-)。这表明这个方法是一个实例方法。正号(+)表明一个类或静态方法。它们不像实例方法那样常见,但 您将会经常看到一些类方法,它们作为创建新的类实例的工厂方法;这免得您必须进行分配和初始化。通过检查我们的应用程序的数据模型来熟悉 Cappuccino 后,我们现在检查视图和事件管理代码。

创建视图并使用代理

需要开发一个交互式富用户界面时,Cocoa 总是技高一筹,部分原因是它提供的灵活的代理模型。Cocoa 成功胜出的另一个有利因素是它提供的工具集。多年来,Mac 和 iOS 开发人员一直受益于 Interface Builder — Cocoa 的一个视觉设计器。这个程序不仅支持用户界面的拖放创建功能,还允许开发人员以图形的方式将 UI 控件 “连接” 到底层应用程序控制器上的方法。其结果是序列化的(“冻干的”)UI 对象,可以直接用于应用程序中。这些文件被序列化为一些 .xib 文件,这是一种 XML 文档类型。Interface Builder 和 .xib 文件是一个强大的组合。Cappuccino 使用一个类似的特性(称为 .cib 文件)来存储 UI 对象。一个名为 Atlas 的工具的使用方式与用于 Cocoa 应用程序的 Interface Builder 非常相似。

同时,您也可以从您的 Cappuccino 应用程序内部、以编程方式创建用户界面。对于简单应用程序,这通常是最简单的方法,因为您的所有代码都在一个位置。这有一点儿违背 MVC 范式,因为您通常是从您的控制器内部创建您的视图,但这在许多情况下都是一种合理简化。清单 3 展示了您的应用程序的控制器。


清单 3. 声明应用程序控制器
@import <Foundation/CPObject.j>
@import <Foundation/CPDictionary.j>
@import <Foundation/CPValue.j>
@import <Foundation/CPURLConnection.j>
@import <Foundation/CPURLRequest.j>
@import <AppKit/CPTableView.j>
@import <AppKit/CPScrollView.j>
@import <AppKit/CPTableColumn.j>
@import "Employee.j"

@implementation AppController : CPObject
{
    CPTableView tableView;
    CPDictionary data;
}

清单 3 中的代码是您的控制器的基本声明。需要注意的第一点是使用了 @import 指令。与 Objective-C 一样,要在 Objective-J 中工作,必须声明您的应用程序的依赖项。实际上,这些依赖项是 Cappuccino 运行时将异步加载的所有独立文件。这不仅有助于管理代码依赖项,还减少了 Cappuccino 的初始 JavaScript 下载,使您的应用程序速度更快。注意清单 3 中您的控制器的实例变量(或 ivar — Cocoa 和 Cappuccino 通常这样称呼它们)。在这里,一个 CPTableView 显示了 CPDictionary 中的数据,后者包含从服务器加载的所有数据。当应用程序准备好开始执行时,清单 4 中的方法将被调用。

清单 4. 应用程序初始化代码
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
    // initialize data
    data = [CPDictionary dictionary];
    
    // create the frame
    var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() 
        styleMask:CPBorderlessBridgeWindowMask];
    var contentView = [theWindow contentView];
    [theWindow setTitle:@"Employee Directory"];
    [theWindow orderFront:self];

    // create the table
    tableView = [[CPTableView alloc] initWithFrame:[contentView bounds]];
    [tableView setAutoresizingMask:CPViewWidthSizable];
    
    // create the columns
    var nameColumn = [[CPTableColumn alloc] initWithIdentifier:@"name"];
    [[nameColumn headerView] setStringValue:@"Name"];
    [[nameColumn headerView] sizeToFit];
    [tableView addTableColumn:nameColumn];
    var phoneColumn = [[CPTableColumn alloc] initWithIdentifier:@"phone"];
    [[phoneColumn headerView] setStringValue:@"Phone"];
    [[phoneColumn headerView] sizeToFit];
    [tableView addTableColumn:phoneColumn];
    var emailColumn = [[CPTableColumn alloc] initWithIdentifier:@"email"];
    [[emailColumn headerView] setStringValue:@"Email"];
    [[emailColumn headerView] sizeToFit];
    [tableView addTableColumn:emailColumn];

    // create scrollable container for table
    var scrollView = [[CPScrollView alloc] initWithFrame:[contentView bounds]];
    [scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
    [scrollView setDocumentView:tableView];
    [scrollView setAutohideScrollers:YES];
    
    [contentView addSubView:scrollView];
    
    [tableView setDelegate:self];
    [tableView setDataSource:self];
    
    // load data from server
    var connection = [CPURLConnection connectionWithRequest:
        [CPURLRequest requestWithURL:"/app/employees.json"] 
        delegate:self]; 
}

清单 4 拥有大量代码,但它实际上非常简单。清单 4 首先关注 UI 代码。首先创建应用程序的主窗口/框架;然后使用 CPTableView 类创建一个数据表,数据表的列,然后是数据表的每一列。最后,创建一个用于放置数据表的 CPScrollView。如果行太多,一屏显示不完,这将使表能够滚动。

清单 4 展示了使用 Cappuccino 创建 UI 对象是多么简单!注意,我们还将表的代理和数据源设置为 self,这意味着控制器将实现代理和数据源方法。

首先,检查最后一行,该行从服务器加载数据。我们使用 CPURLConnectionCPURLRequest 来请求这个数据。这个操作使用一个经典 Ajax 样式 XMLHttpRequest 在后台进行。但是,一旦您将代理设置为 self,就应该为连接指定一个代理,而不是一个回调方法。这样,当服务器响应来自应用程序的请求时,控制器上的一个方法将被调用。CPURLConnection 的代理类定义将被调用的方法的名的类。在这里,代理有 4 个方法: connection:didFailWithError:connection:didReceiveResponse:connection:didReceiveData:connectionDidFinishLoading:。由于代理是控制器类,因此您可以实现那 4 个方法中的任何一个 — 它们都是可选的。在这里,忽略错误事例,且只实现 connection:didReceiveData: 方法,如 清单 5 所示。


清单 5. 连接代理方法
// connection handler
- (void)connection:(CPURLConnection) connection didReceiveData:(CPString)sData
{
    var result = CPJSObjectCreateWithJSON(sData);
    data = [CPDictionary dictionaryWithJSObject:result];
    connection = nil;
    [tableView reloadData];
}

清单 5 中的方法将在服务器返回一个成功响应时被调用。服务器的响应将是传递到该方法的 sData 字符串。使用另一个 Cappuccino 函数 CPJSObjectCreateWithJSON 将数据解析为一个 JavaScript 对象。然后使用那个 JavaScript 对象创建一个 CPDictionary。 最后,重新加载数据表,显示来自服务器的新数据。这个示例极好地展示了 Cappuccino 不只是 Cocoa 的一个端口,而是用于 web 的 Cocoa,添加了很多简化 web 开发的特性。简要回顾一下清单 4,您在那里将数据表的数据源定义为控制器,那意味着控制器有一些数据源方法需要实现,如 清单 6 所示。

清单 6. 数据表源方法
// data source methods
- (int)numberOfRowsInTableView:(CPTableView)tView
{
    return [data count];
}

- (id)tableView:(CPTableView)tView objectValueForTableColumn:
(CPTableColumn)tableColumn row:(int)row
{
    return [data[row] objectForKey:[tableColumn identifier]];
}

在清单 6 中可以看到,实现数据源合同需要两个方法。第一个是 numberOfRowsInTableView:,行数只是 CPDictionary 的大小。更有趣的方法是 tableView:objectValueForTableColumn:row: 方法,对数据表中的每个单元都需要调用该方法。只需将这个方法从来自服务器的响应创建的 CPDictionary 中提取出来。这个示例很好地展示了可以使您的 Objective-J 代码非常紧凑的嵌套式方法调用。查看控制器实现的数据源方法之后,我们来看看控制器实现的代理方法,如 清单 7 所示。

清单 7. 表代理方法
// event handlers
- (void)tableView:(CPTableView)tView sortDescriptorsDidChange:(CPArray)oldDescriptors
{
    [data sortUsingDescriptors:[tView sortDescriptors]];
    [tView reloadData];
}

CPTableView 的代理上定义了几个方法,每个方法都对应一个数据表可以发起的事件。在这里,我们只对一个事件感兴趣 — 某人单击表头对数据表重新排序 — 因此您只需实现对应的方法:tableView:sortDescriptorsDidChange:。您可以在这里看到 Cocoa 的便捷性:您只需从数据表获取它的新排序描述符,使用它们来对 CPDictionary 重新排序,然后重新加载数据表。这将导致清单 6 中的 tableView:objectValueForTableColumn:row: 方法针对数据表中的每个单元再次调用,从而显示经过重新排序的数据。浏览了 Cappuccino 的几个强大特性后,我们来考虑如何将其用于移动 web 应用程序。

Cappuccino 和移动 web

尽管我们开发的简单应用程序展示了 Cappuccino 的许多核心功能,但它无法展示 Cappuccino 和移动 web 之间的关系。为了更好地理解 Cappuccino 有多么适合移动 web 应用程序,我们看看 Cappuccino 激发的框架 — Cocoa。如果您返回 清单 3 并查看它的导入语句,您将注意到每个 UI 元素(CPTableViewCPScrollViewCPTableColumn) 都来自 AppKit 模块。在 Cocoa 开发世界中,AppKit 是桌面应用程序的 UI 模块。但是,如果您以前只从事 iOS 开发,那么您可能对 AppKit 不熟悉,因为它不是 iOS SDK 的一部分。相反,取而代之的是 UIKit。这些 UI 元素专门针对 iPhone 设计,而不是针对桌面应用程序设计的。Cappuccino 包含了许多来自 AppKit 的类,但没有一个类来自 UIKit。 换句话说,它只包含针对桌面应用程序设计的 UI 元素,没有包含任何专门针对移动设备设计的 UI 元素.因此,在将 Cappuccino 的任何 AppKit 类用于移动 web 应用程序之前,您最好三思而行。但是,您仍然可以使用低级 API 或 Cappuccino 的 CoreGraphics 库来创建您自己的自定义 UI 元素。

结束语

本文探索了 Cappuccino 框架的许多特性。该框架采用了一种编程方法来在客户机上创建您的所有 UI,且只从服务器获取数据。这种架构对于移动 web 应用程序而言是最优的。它还向您提供了一个优雅的应用程序模型,利用代理模式来从您的应用程序消除大量重复代码(boilerplate code)。另一方面,为更好地利用 Cappuccino,您需要学习 Objective-J 语言,这对于 web 开发人员来说也许不是小菜一碟。更重要的是,它的 UI 元素不是针对移动用途而设计的。但那并不一定意味着它们不会有效,但的确意味着它们可能不会利用触摸事件这样的事情。这个框架提供的开发简便性 — 特别是与它的 Atlas 工具相结合时 — 仍旧可能是一个不错的平衡,这要取决于您的应用程序的复杂程度。

范例代码: cappdir.zip 文章出处: IBM developerWorks

加载中
0
mallon
mallon
跨平台的吗?
返回顶部
顶部