将 Google 的云计算功能连接到 Apple 的 iPhone 中

针对手持设备的云计算和软件开发是两项非常热门的技术,并被越来越多地结合起来用于创建混合解决方案。在本文中,了解如何连接 Google App Engine(Google 的云计算功能)和 iPhone(Apple 的移动平台),以及如何通过连接到 App Engine 云并缓存应用程序数据以备离线使用,从而利用开源库 TouchEngine 动态控制 iPhone 上的应用程序数据。

Noah Gift, 创始人, GiftCS, LLC

Photo of Noah Gift Noah Gift 是 O'Reilly 出版的 Python For Unix and Linux System Administration 一书的合著者,并且现在还在为 Manning 编著 Google App Engine In Action 一书。他是一名作家、演说家、顾问和社区负责人,并为 IBM developerWorks、Red Hat MagazineO'Reilly 和 MacTech 撰稿。他的咨询公司的网站是 http://www.giftcs.com,他的个人网站是 http://noahgift.com。 Noah 拥有加州洛杉矶的 CIS 的硕士学位,加州 Poly San Luis Obispo 的营养科学学士学位,他还是通过 Apple 和 LPI 认证的系统管理员,他曾经在许多公司工作过,如加利福尼亚理工学院、Disney Feature Animation、Sony Imageworks 和 Turner Studios。他目前在新西兰的 Weta Digital 工作。在空闲的时候,他喜欢和妻子 Leah 以及他们的儿子 Liam 一起度过,谱写钢琴曲、参加马拉松比赛以及积极地参与体育活动。


developerWorks 投稿作者

Jonathan Saggau, 创始人和 CEO, EMC

Jonathan Saggau 的照片Jonathan Saggau 是 Sounds Broken 公司的创始人和 CEO,该公司是一家 Mac OS X 和 iPhone 软件的签约店,也是一家技术和业务咨询公司。除了乘飞机出差或对硬件和软件实施反向工程外,他通常都会与客户(比如 Equity Audio、Innovative Audio 和 Big Nerd Ranch)一起开发优秀的产品、服务和流程。他的博客在 jonathansaggau.com/blog/,也可通过 twitter 站点的 _cnnew1@jonmarimba 联系到他。



2009 年 2 月 17 日

简介

过去几年出现了很多创新技术,2008 年对技术而言是不同凡响的一年。两项最让人兴奋的创新是云计算和移动应用程序开发。在本文中,我们将探究一种通信方法,这种方法能利用这两个技术来实现协作开发人员的梦想。在本文中,我们将使用 Google App Engine(Google 的云计算平台)和 iPhone(Apple 的移动平台)来开发一个能同步 “云” 数据的应用程序。

我们将利用一种简单的方法来从 App Engine 拉出数据放到 iPhone 上; 这种方法需要大量使用 python 和 App Engine。使用 RSS、ATOM 或 REST 将数据连锁到 iPhone 的常规方法非常简单,但是必须要编写一个解析器。更简单的一种做法是使用 XML 属性列表或 plist。根据属性列表的手册页面(参见 参考资料):“属性列表使用几个核心基础类型将数据组织成指定的值和值的列表,这些类型包括 CFString、CFNumber、CFBoolean、CFDate、CFData、CFArray 和 CFDictionary。借助这些类型,您就能够生成结构良好、可传输、可存储和可访问的数据,并且还尽可能提高了效率。”

plist 消除了在 iPhone 上解析 XML 的烦扰,因为这些 plist 是 XML 文件格式的,Cocoa Touch 可以很容易将其解析并转变成有意义的对象。在 App Engine 上使用 Python 内的 plist 库,不用费什么力气就能将任意一个简单 Python 库对象发送给 iPhone,但前提是 Python 库内的数据类型是 plist 允许的。本文展示了使用 TouchEngine 开源库开发应用程序以便查看莎士比亚的十四行诗。要获得 Google Code 项目的链接,请参见 参考资料

背景

首先,让我们先来看看有关 iPhone SDK 和 Google App Engine 的背景信息。

iPhone SDK

Native iPhone SDK 可通过 Objective-C 语言得到。它非常类似于 Mac OS X® 上的 Cocoa 编程,包括了能充分利用 iPhone 独特特性的一些 API,比如 GPS、触摸屏(multi-touch)、加速器(accelerometer)以及屏幕键盘。将来的功能还将包括对通知自动推入(push notification)等技术的支持。有关 iPhone Native SDK 的更多信息,请参见 参考资料

对于移动应用程序开发人员而言,iPhone 提供了丰富的开发环境。直到最近,Objective-C 对 很多开发人员而言仍旧是一种相当深奥的语言,因为它只用于 NeXT 和 Apple,但是现在通过 Cocoa Touch SDK,它的支持者开始多了起来。借助 iPhone,Objective-C 更是成为了全部新一代移动应用程序开发人员的前沿和中心。

何为 Google App Engine?

有了 Amazon 的 S3 存储和 EC2 弹性计算服务,云计算在可视化方面得到了很大的推进。Google App Engine 是基于服务的云计算市场的一个新生力量。Google App Engine 为著名的 Google 可伸缩数据中心提供了一个 Python 语言的 API(将来会出现其他语言的版本)。这是一个极大的变革,它让软件开发人员能够从管理应用程序伸缩性的固有复杂性中解脱出来,让他们能将精力集中于应用程序的编写。

从 Google App Engine 生成 plist 文件

我们先来看看如何从 Google App Engine 生成 plist 文件,之后,您会通过 iPhone Cocoa Touch SDK 在 iPhone 上使用该文件。由于 App Engine 起初是免费的,所以它成为了移动应用程序开发人员的一种有趣的原型化方法。此外,此 API 是 Python 版本,而该语言享有开发迅速的美誉;而且它还是一种解释效率很高的语言。通过 App Engine 和 Python 将 iPhone 应用程序的繁重任务以及数据存储外包给 “云功能”,是一种非常有益的做法。

要跟随本文进行操作,需要下载 App Engine SDK(参见 参考资料 以获得最新版本)。有了 App Engine,很容易就能让一个 protype 在几分钟内工作起来。请注意,您也可以从本文附带的源代码下载此示例。

为了将 plist 文件提供给 iPhone 应用程序使用,只需将 App Engine project 目录内的 plistlib.py 包括进来,稍微修改一下 main.py 脚本,再包括进 sonnet.py。Sonnet.py 是一个 Python 源文件,其中的一个目录包含所有莎士比亚十四行诗的文本。清单 1 所示的就是这个 main.py 文件。

清单 1. main.py
#!/usr/bin/env python
#Python sonnet maker

import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

#external imports
import sonnet
import plistlib

class MainHandler(webapp.RequestHandler):
    """Returns sonnets dictionary as a converted plist"""
    def get(self):
        plist = plistlib.writeplistToString(sonnet.verses)
        self.response.out.write(plist)

def main():
    application = webapp.WSGIApplication([('/plists/sonnets', MainHandler),
                                        ],
                                        debug=True)
    run_wsgi_app(application)


if __name__ == '__main__':
  main()

上面这个代码片段将包含莎士比亚十四行诗的字典的内容转变成一个 XML plist,并将其提供给任何请求此 /plists/sonnets URL 的客户机。不管您相信与否,这就是我们的这个 Google App Engine 应用程序的主体。清单 2 给出了 sonnet.py 的一小部分。

清单 2. sonnet.py 的示例
verses={"verses":[["I","""FROM fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou, contracted to thine own bright eyes,
Feed'st thy light'st flame with self-substantial fuel,
Making a famine where abundance lies,
Thyself thy foe, to thy sweet self too cruel.
Thou that art now the world's fresh ornament
And only herald to the gaudy spring,
Within thine own bud buriest thy content
And, tender churl, makest waste in niggarding.
  Pity the world, or else this glutton be,
  To eat the world's due, by the grave and thee."""]}

[NOTE: EDITED FOR SPACE]

main 函数将 URL /plists/sonnets 传递到类 MainHandler。如果此客户机通过 HTTP GET 请求数据,就会返回类似清单 3 的结果。

清单 3. HTTP Get 的结果
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD plist 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>verses</key>
    <array>
        <array>
            <string>I</string>
            <string>FROM fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou, contracted to thine own bright eyes,
Feed'st thy light'st flame with self-substantial fuel,
Making a famine where abundance lies,
Thyself thy foe, to thy sweet self too cruel.
Thou that art now the world's fresh ornament
And only herald to the gaudy spring,
Within thine own bud buriest thy content
And, tender churl, makest waste in niggarding.
  Pity the world, or else this glutton be,
  To eat the world's due, by the grave and thee.</string>
        </array>
        <array>
[NOTE: EDITED FOR SPACE]

访问 isonnet 项目主页(相关链接,请参考 参考资料),可以看到此输出。此 URL 给出的是一个编辑后的莎士比亚十四行诗的完整 plist 表示。不过,请注意,您的浏览器可能将此显示为一个巨大的纯文本文件。XML plist 是有效的 XML 而且大多数浏览器都会试图显示它。请参阅页面的源代码,查看格式化的 plist。

Python 和 App Engine 示例的更详细信息以及有关 Google App Engine 的高级教程的链接,请参看 参考资料。但是,目前,让我们先来看看一个 iPhone 应用程序是如何接受此 plist 数据来更改应用程序数据的。

创建一个能从 Google App Engine 动态读取并缓存 XML plist 文件的 iPhone 应用程序

TouchEngine 包含一组对象,使得在 iPhone 上下载和缓存 XML plist 变得十分简单。我们用来下载和缓存 sonnet plist 所用的对象在其头文件 GRplistController.h 内描述,如清单 4 所示。

清单 4. GRplistController.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "Reachability.h"
#import "GRplistControllerDelegateProtocol.h"

typedef enum {
    kGRplistDownloadCannotInitiate = 0,
    kGRplistConnectionFailure,
    kGRplistFileFormatFailure
} GRErrorCode;

#define GR_ERROR_DOMAIN @"GRplistController_Error_Domain"

@class GRplistModel;

// This class will grab a remote plist from the server whenever update 
// is called.

// Also registers with the Reachability object for notifications when
// the remote host changes availability and updates accordingly
// asking the delegate first if downloading new data is desirable.

@interface GRplistController : NSObject {
    NSURL *remoteURL;
    NSObject <GRplistControllerDelegate> *delegate;

    NetworkStatus remoteHostStatus;
    NetworkStatus internetConnectionStatus;
    NetworkStatus localWiFiConnectionStatus;
    BOOL loadingData;
@private
    GRplistModel *_model;
    BOOL hostIsReachable;
    NSMutableDictionary *plistIndex;
    NSMutableData *receivedData;
    NSURLConnection *connection;
}

@property(nonatomic, retain)NSURL *remoteURL;
@property(nonatomic, assign)NSObject *delegate;
@property(nonatomic, readonly)GRplistModel *model;

//date of last download
@property(nonatomic, retain)NSDate *lastUpdate;
@property(nonatomic, getter=isLoadingData)BOOL loadingData;
@property NetworkStatus remoteHostStatus;
@property NetworkStatus internetConnectionStatus;
@property NetworkStatus localWiFiConnectionStatus;

//designated Initializer
- (id)initWithRemoteURL:(NSURL *)aRemoteURL;

- (void)updateDataFromDisk;
- (void)download;
- (void)cancelDownload;
@end

为了使用此类,我们提供了一个 NSURL,XML plist 就位于其中,我们告知它从磁盘下载或拉取数据。GRplistController 在此用户的数据存储目录生成一个 plist 字典文件,以便存储组成远程 URL 的缓存后的 plist 的位置和最近一次下载日期。plist 被下载一次后,后续的数据加载就可直接从磁盘进行。另外,还可以针对给定 URL 的最后一次下载的日期和时间使用 lastUpdate 属性来查询 GRplistController 对象,并决定何时从 Web 刷新数据。GRplistController 总是使用 NSURLConnection 异步下载数据,这样用户界面不会在等待新数据时冻结起来。如果新的数据不可用或不可访问,可以继续使用所缓存的数据,直至新数据可用且可完全下载。您还可以将一个对象设为 GRplistController 的一个代理(delegate),借此就可对数据下载进行细粒度控制,就能在更新数据到来时提供通知,在远端数据被证实不可访问时提供详细的错误报告。GRplistController 的这些代理方法在 GRplistControllerDelegateProtocol.h 内定义,如清单 5 所示。

清单 5. GRplistControllerDelegateProtocol.h
@class GRplistController;

@protocol GRplistControllerDelegate

@optional

// the list controller will automatically try to update data when the network status
// changes, so it's asking permission.
- (BOOL)listControllerShouldDownloadRemoteData:(GRplistController *)listController;
- (void)listController:(GRplistController *)
listController downloadDidFailWithError:(NSError *)err;

// if the data from the server has changed...
- (void)listControllerDataWillChange:(GRplistController *)listController;
- (void)listControllerDataDidChange:(GRplistController *)listController;

@end

我们的 iPhone 演示应用程序 Sonnet 的源代码包含在本文后面的 下载 部分,此应用程序能从我们在 App Engine 服务器上的项目拉取所有莎士比亚十四行诗。这就让我们能不时地上传修正(比如拼写错误、不准确之处等),而在此之前,这常常需要进行重新编译和应用程序更新(如果数据由应用程序附带的话)。我们既希望能不时地更新通用的应用程序数据,又希望能够避免进行应用程序重编译,因为我们的 UI 并未更改。这就让应用程序数据更新能够与特性添加和 bug 修复区分开来。而且,通过在连接到 Internet 的时候启动应用程序,用户总是可以拥有最新的数据,而不像原来那样,必须等待我们应用程序的更新出现在 iPhone Application Store,而这要花些时间。

Sonnet 的用户界面非常简单。应用程序首先用 UITableView 加载 RootViewController,而后者会立即显示来自所有可用缓存数据的每个十四行诗的前三行,并会在之后显示任何被更新的数据。

图 1. 表视图 iSonnet
表视图 iSonnet

如果用户触及了此表视图内的一个单元格,RootViewController 就会将 GRSonnetViewController 推到屏幕上来。GRSonnetViewController 之后会显示相应的完整的十四行诗。

图 2. Sonnet 视图
Sonnet 视图

第一次运行时,应用程序没有数据显示给用户,所以在试图从 App Engine 服务器拉取数据却没有获得数据时最好是显示错误消息。否则,用户将会看到一个空白的表视图。(应用程序的正式版本将会在应用程序包中包括数据,但演示应用程序却没有。)

图 3. 显示错误
显示错误

这是惟一一次应用程序向用户显示网络通信错误的时候,因为在所有其他情况下应用程序已经下载并缓存了数据。有关 UI 实现的细节,可以参考 示例代码

在 Sonnet,RootViewController 对象充当了 GRPlistController 的一个代理。RootViewController 的初始化代码如清单 6 所示。

清单 6 - RootViewController 初始化代码
(void)viewDidLoad {
   [super viewDidLoad];
   // Add the following line if you want the list to be editable
   NSString *rootURL = [[self class] defaultURL];
   self.sonnetsController = [[[GRplistController alloc] 
       initWithRemoteURL:[NSURL URLWithString:rootURL]] autorelease];
   self.sonnetsController.delegate = self;

   [self.sonnetsController updateDataFromDisk];
   self.sonnets = nil;
   [self updateSonnetsFromModel];

   //hit the web for new information
   [self updateSonnets];
  }

在 Sonnet 启动时,RootViewController 先是试图从磁盘加载所缓存的 plist 数据。如果所缓存的数据可用,它就会立即被载入到应用程序内以便应用程序能立即可用。一旦载入了任何缓存数据,应用程序就会查询 App Engine 站点以便异步获得新数据(有关 NSURLConnection 实现的细节,参见 “#pragma mark Downloading of data” 行下面的 GRplistController.m )。如有新数据,此新数据会与缓存数据比较,如果发现修改,GRPlistController 就会通过 listControllerDataDidChange 方法通知 RootViewController。而 RootViewController 之后会重新载入带有新数据的十四行诗表。

结束语

App Engine 和 iPhone 开发综合在一起就形成了一种功能强大的工具,可用于编写和原型化移动应用程序。这个示例 Web 应用程序支持着一个小型的 iPhone 应用程序 Sonnet,这个小程序可从 Apple 的 Application Store 免费下载。综合 Google App Engine 和 iPhone 开发的强大之处在于可以使用 Python 快速原型化应用程序的某些部分,在这之前用 Objective-C 进行编码十分繁琐,另外这种综合也增强了(在线和离线)数据存储的灵活性。TouchEngine 汇集了各种优点于一身。借助 TouchEngine,混合应用程序的开发人员可以很容易地编写软件,实现在异步更新云数据的同时在 iPhone 上本地缓存数据。这就让应用程序既能迅速响应用户输入,同时又能在线维护数据以便及时更新。

iPhone 和 Google App Engine 开发人员社区均提供了丰富的资源,可用来加速开发。如果您对面向这两个平台的开发或综合两平台的特性(如本文所示)感兴趣,我们建议您详细阅读官方文档的相关内容。Google 和 Apple 均有很棒的书面教程,某些情况下,也都提供有基于视频的教程。Google App Engine 还在全球范围内举行了 “Hack-A-Thon” 活动,您不妨用 Google 查查,看在您所在地区是否有这样的活动。Apple 的 WWDC 会议也是面向 iPhone SDK 程序员的一个很有价值的资源。


下载

描述名字大小
示例 iPhone 和 Google 应用程序引擎代码iPhone_Google_App_Engine_code.zip2919KB

参考资料

学习

获得产品和技术

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=369910
ArticleTitle=将 Google 的云计算功能连接到 Apple 的 iPhone 中
publish-date=02172009