ios数据库应用高级编程


[美] Patrick Alessi 著 冯宝隆     译 移动开发经典丛书 Join the discussion @ p2p.wrox.com Wrox Programmer to ProgrammerTM   如果你是一名有经验的开发者并对开发以数据为中心的 iPhone和iPad应用程序感兴趣,那么《iOS数据库应用高级编 程(第2版)》恰好适合你。这本必须拥有的书进行了新的充分 修订,并且在结构上对应企业应用程序的数据流。该书作者 Patrick Alessi经验丰富,他先向你演示了如何从大规模数据库 获取数据并将数据放入设备上和显示该数据。然后讲解了如 何直接在设备上建立数据,并和Web服务通信。在本书的最 后,你将能自信地为iPhone和iPad实现数据驱动应用程序并将 iOS应用程序和现有的企业系统集成。 主要内容 ◆ 包含完整的重建代码、屏幕截图和iOS 6中与数据库编程 和企业集成有关的所有新特性 ◆ 向你演示了如何使用SQLite存储数据,如何使用Core Data API建模和管理数据,以及如何利用iPhone和iPad的 内置功能 ◆ 涵盖了广泛使用的UITableView控件,包括自定义数据 显示的不同策略 ◆ 讲述了如何在iPhone上处理和建立XML 作者简介   Patrick Alessi为包括小型企业和美国空军在内的客户建 立以数据为中心的应用程序。他建立了MotivationalQuotes 和CNodes应用。他是Professional iPhone and iPad Database Application Programming和《iOS游戏开发入门经典》的作 者。当前,他努力专注于为移动设备建立移动和互联应用 程序。 源代码下载及技术支持   http://www.wrox.com   http://www.tupwk.com.cn/downpage 应用开发的超值工具、技术和API 定价:59.80元 wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming questions about this book, join discussions on the hottest topics in the industry, and connect with fellow programmers from around the world. Code Downloads Take advantage of free code samples from this book, as well as code samples from hundreds of other books, all ready to use. Read More Find articles, ebooks, sample chapters and tables of contents for hundreds of books, and more reference resources on programming topics that matter to you. A Wiley Brand iOS 数据库 应 用高 级 编 程 ( 第2版 ) 移动开发经典丛书 iOS 数据库应用高级编程 (第 2 版) [美] Patrick Alessi 著 冯宝隆 译 北 京 Patrick Alessi Professional iOS Database Application Programming, Second Edition EISBN:978-1-118-39184-6 Copyright © 2013 by John Wiley & Sons, Inc., Indianapolis, Indiana All Rights Reserved. This translation published under license. 本书中文简体字版由 Wiley Publishing, Inc. 授权清华大学出版社出版。未经出版者书面许可,不得以任何方式 复制或抄袭本书内容。 北京市版权局著作权合同登记号 图字:01-2014-0635 Copies of this book sold without a Wiley sticker on the cover are unauthorized and illegal. 本书封面贴有 Wiley 公司防伪标签,无标签者不得销售。 版权所有,侵权必究。侵权举报电话:010-62782989 13701121933 图书在版编目(CIP)数据 IOS数据库应用高级编程 / (美)艾烈希(Alessi, P, ) 著;冯宝隆 译. —2版. —北京:清华大学出版社,2014 (移动开发经典丛书) 书名原文:Professional IOS Database Application Programming, Second Edition ISBN ISBN 978-7-302-36956-1 Ⅰ. ①i… Ⅱ. ①艾… ②冯… Ⅲ. ①移动终端—应用程序—程序设计 Ⅳ. TP① 929.53 中国版本图书馆 CIP 数据核字(2014)第 135546 号 责任编辑:王 军 于 平 装帧设计:孔祥峰 责任校对:曹 阳 责任印制: 出版发行:清华大学出版社 网 址:http://www.tup.com.cn,http://www.wqbook.com 地 址:北京清华大学学研大厦 A 座 邮 编:100084 社 总 机:010-62770175 邮 购:010-62786544 投稿与读者服务:010-62776969,c-service@tup.tsinghua.edu.cn 质 量 反 馈:010-62772015,zhiliang@tup.tsinghua.edu.cn 印 刷 者: 装 订 者: 经 销:全国新华书店 开 本:185mm×260mm 印 张:22.25 字 数:541 千字 版 次:2014 年 7 月第 1 版 印 次:2014 年 7 月第 1 次印刷 印 数:1~3000 定 价:59.80 元 —————————————————————————————————————————————— 产品编号: 作 者 简 介 在 1980 年,当 Patrick Alessi 第一次看到他的名字在终端上闪过时,他就迷恋上了编 写计算机程序。从那以后,他开始使用各种语言为他能获得的硬件平台编写软件,包括在 他接受工程教育期间对 VA X 系统上的 Fortran 语言的短暂而痛苦的尝试。Patrick 从美国罗 格斯大学获得了土木工程学士学位,然后又从美国史蒂文斯理工学院获得了计算机科学学 士学位。 在专业领域,Patrick 主要研究以数据为中心的应用程序,其客户范围小到小型企业数 据库,大到美国空军的大规模系统。当前,他关注于移动开发的前景,并为如 iPhone 和 iPad 这样的移动设备开发网络应用程序。 Patrick 工作之余喜欢玩游戏(尤其是“星际争霸”)、摄影、旅行和与家人在一起。你 可以关注他的推特 pwalessi,或访问他的博客 iphonedevsphere.blogspot.com。 技术编辑简介 Michael Gilbert 是一名在各种工程公司工作过的资深系统程序员。他的第一个游戏是 在 Atari ST 上开发的,并且他是 STart 杂志的特约编辑,经常在该杂志上发表文章。多年 以来,他一直在为世界范围内的用户开发 PC 和 Mac 上的游戏软件。他还是一位专业的 Flash ActionScript 程序员,并且开发了一个名为 HigherGames 的流行互联网游戏环境。现在他喜 欢开发 iPhone 和 iPad 游戏,并且当前已经在 App Store 上发布了几款游戏,并且将来还会 发布更多。在业余时间他喜欢和他的妻子 Janeen 一起玩拼字游戏。 致 谢 我要借此机会感谢每位促成这本书出版的人。首先我要感谢我的策划编辑Mary James, 本书的第 2 版在她的领导下完成了策划过程。其次我要感谢我的项目编辑 Brian MacDonald,他帮助我解决了在写作和出版过程中出现的每个问题。我还要感谢我的技术 编辑 Mike Gilbert,他利用宝贵的应用开发时间来审查我的作品。最后我要感谢所有其他编 辑和制作人员,他们投入了大量时间在本项目上从而使它最终能够印刷。 对于我的妻子 Cheryl 和我的继女 Morgan,我无论如何感谢都不为过。在我编写本书 的整个期间,她们容忍了我的坏脾气,还容忍了我没时间参加有趣的家庭活动。在我编写 本书的工作时她们操持家务。你们对我的耐心是令人震惊的。我还想要感谢我的妈妈,在 我非常小的时候她就引导我学习计算机并教会了我计算机的基础知识。最后我想要感谢我 的爸爸,他促使我努力工作并让我明白了如何成为一名父亲。 前 言 随着 iPhone 的推出,苹果公司彻底改变了移动计算市场。iPhone 将移动电话从一台用 来打电话、检查电子邮件和查找电影场次的设备转换为一台几乎可以运行任何类型的应用 程序的计算机。自从 iPhone 在 2007 年发布以来,开发人员已经编写了超过 70 万种可在 iOS 设备上运行的应用程序。这些应用程序可分为很多种类型,包括游戏、实用工具、社交网 络、参考、导航和商务等类型。 计算领域的趋势正在趋向于可移动性和移动平台,如 iPhone 和 iPad,并且正在远离基 于桌面的环境。尤其在商业和企业环境中,决策者想要一天 24 小时方便快捷地访问他们的 数据。iPhone 和 iPad 是移动计算的理想平台,因为它们具有合适的结构因素和广泛的库和 API 集合。 尽管市场上有很多非常好的 iOS 软件开发方面的书籍,但我不能找到一本专门面向企 业开发者的书籍,这些企业开发者需要使企业数据或商业应用程序具有可移动性。我编写 此书的最初目的是向这些开发者提供这样的信息,他们可用这些信息从后台服务器获得企 业数据,并在可移动设备上显示和操作这些数据,然后获得合适的返回给他们的企业信息 系统的信息。 在我编写这本书的过程中,有一个事实变得清晰起来,那就是除了我在开始时提到的 业务用例,我在本书中介绍的工具和技术还适合大多数应用程序类型。任何类型的应用程 序的开发者,只要他需要在 iOS 上存储数据,就都会对 Core Data API 的广泛覆盖率感兴趣。 任何想要发送数据到如 Facebook 或 Twitter 这样的外部 Web 服务的开发者都能受益于本书 的处理 XML 和 Web 服务的章节。许多应用程序都需要使用表显示数据,对此我也会详细 介绍。最后,所有 iOS 应用程序都有一个用户界面,我将介绍如何使用故事板构建用户界 面。尽管我的最初想法是为企业开发者编写一本书,但我相信我写的书几乎对开发任何类 型的应用程序都有用。 此版本的新内容 自本书的第 1 版发行以来,iOS 开发社区已发生了很大变化。苹果公司持续发布具有 新特性的 iOS 新版本来帮助开发者为苹果设备构建更好的应用程序。 通过引入 Automatic Reference Counting(ARC),苹果公司已极大简化了 iOS 应用程序 中的内存管理工作。开发者不再需要手工保留或释放内存,而是由 ARC 处理这些。因此, 我更新了本书的示例来实施 ARC 和与 ARC 兼容。 苹果公司还通过提供故事板来精简 iOS 用户界面的开发。故事板替代了 Interface iOS 数据库应用高级编程(第 2 版) VI Builder 原来提供的功能。现在你可以在 Xcode 的故事板中处理所有用户界面工作,我已经 修改了上一版的代码和示例以在合适的地方使用故事板。 最后,所有屏幕截图和许多其他图表都已被重画以反映这些或其他一些自第 1 版以来 在 iOS 生态系统中进行开发的变化。 本书读者对象 如前所述,我最初是为企业开发者编写此书的,他们主要负责移动化企业数据和编写 在移动设备上呈现和操作这些数据的应用程序。在写作这本书期间,我开始确信我正在介 绍的工具、API 和开发技术对于商业领域之外的许多种类的应用程序开发都是有价值的。 任何编写应用程序以任何方式处理数据的人都应该会发现本书很有用。 本书应该不是你的第一本 iOS 应用程序开发书籍。在本书中你找不到“Hello World” iOS 应用程序。有很多好书可用来学习如何构建基本 iOS 应用程序。本书主要针对已经理 解如何构建 iOS 应用程序的开发者,他们知道如何使用 Interface Builder 设计和构建用户界 面,并且熟练掌握了 Objective-C。这并不是说初学者无法在这里找到有用的知识,只是 我在编写这本书时认为读者已经理解了 iOS 应用程序的基本架构并能够熟练地使用 Xcode 工具。 本书内容 本书讲述了用于理解如何构建以数据为中心的 iOS 应用程序的技术。你将会发现和 SQLite 有关的章节,SQLite 是作为 iOS 的一部分而被包含在其中的数据库引擎。此处你将 学习如何从各种格式的文件中导入数据到数据库中和如何在设备上查询数据。我将广泛地 介绍 UITableView 控件,包括自定义数据显示的不同策略。另外我将介绍如何使用故事板 构建应用程序用户界面。你还将看到 Core Data API 的广泛使用。当你需要在设备上构建和 存储数据时,你将发现你会经常使用这个极好的数据持久存储框架。最后将介绍如何在 iOS 上构建 XML 和如何将你的应用程序与 Web 服务集成。 本书结构 我将本书分为三个部分,它们宽松地对应企业应用程序中的数据流。本书的第Ⅰ部分 介绍了如何从如 Oracle、MySQL 或 SQL Server 这样的大型数据库获取数据,以及如何将 这些数据存储到设备中并显示。本书的第Ⅱ部分介绍了如何在设备上构建数据和 Core Data API。本书的最后一部分介绍了如何从设备获取数据和与 Web 服务通信。尽管我尝试以一 种逻辑顺序逐章呈现这些材料,但读者不需要按顺序阅读本书。如果你正在构建基于表视 图的应用程序并需要知道如何定制表的外观,则你只需跳转到第 3 章。如果你正在构建针 对 iPad 的应用程序,则只需要阅读第 4 章。如果你需要实现 Core Data,可跳转到第Ⅱ部 前 言 VII 分。如果你需要集成 Web 服务,可参考第 10 章和第 11 章。 阅读本书需要做的准备 因为这本书适合中到高级 iOS 开发者,所以你应该已经具有了阅读本书所需的所有工 具。你需要一台安装有 Mac OS X 的苹果计算机来构建 iOS 应用程序。另外你需要安装 Xcode 开发环境,它由苹果公司在 Mac 应用商店免费提供。 最后一个需求是如果你想要在物理设备上安装你的应用程序,而不是仅在 iOS 模拟器 上运行你的代码,还需要加入 iOS 开发者计划。在编写这本书时,加入该计划每年需花费 99 美元,从而允许你在你的设备上构建和运行应用程序,并可将最终完成的应用程序提交 给苹果 iOS 应用商店用于销售。如果你当前不是开发者计划成员,不要担心。本书中只有 很少一部分代码需要在真实设备上运行,而本书中几乎所有代码都可在模拟器中正确运行。 对于需要在设备上运行的代码,我会在文本中做出标注。 源代码 当你阅读本书的源代码时,你可以选择手工输入所有代码,或者使用本书附带的源代 码文件。本书使用的所有源代码都可从 www.wrox.com 下载,具体对于本书来说,其源代 码可从 www.wrox.com/remtitle.cgi?isbn=1118391845 的 Download Code 选项卡处下载。 你还可以在 www.wrox.com 处通过 EISBN(本书的 EISBN 为 978-1-1183-9184-6)搜索 本书来找到其源代码。www.wrox.com/dynamic/books/download.aspx 处提供了当前所有 Wrox 书籍的完整代码下载列表。 在每一章中,你可在清单标题和文本中根据需要查找代码文件名称的引用。 大多数 www.wrox.com 中的代码都以.ZIP、.RAR 或类似的适合特定平台的文档格式 压缩。下载这些代码后,你只需要使用合适的压缩工具解压缩。 勘误表 尽管我们已经尽了最大的努力来保证文章或代码中不出现错误,但是错误总是难免 的,如果你在本书中找到了错误,例如拼写错误或代码错误,请告诉我们,我们将非常感 激。通过勘误表,可以让其他读者避免走入误区,当然,这还有助于提供更高质量的信息。 要在网站上找到本书英文版的勘误表,可以登录 www.wrox.com/remtitle.cgi?isbn= 1118311809,或者访问 http://www.wrox.com,通过 Search 工具或书名列表查找本书,然后 在本书的细目页面上,单击 Errata 链接。在这个页面上可以查看到 Wrox 编辑已提交和粘 贴的所有勘误项。完整的图书列表还包括每本书的勘误表,网址是 www.wrox.com/misc-pages/ booklist.shtml。 如果你在勘误表上没有找到错误,那么可以到 www.wrox.com/contact/techsupport.shtml iOS 数据库应用高级编程(第 2 版) VIII 上,完成上面的表格,并把找到的错误发送给我们。我们将会核查这些信息,如果无误的 话,会把它放置到本书的勘误表中,并在本书的后续版本中更正这些问题。 p2p.wrox.com 要与作者和同行讨论,请加入 p2p.wrox.com 上的 P2P 论坛。这个论坛是一个基于 Web 的系统,便于你张贴与 Wrox 图书相关的消息和相关技术,与其他读者和技术用户交流心得。 该论坛提供了订阅功能,当论坛上有新的消息时,它可以给你传送感兴趣的论题。Wrox 作者、编辑和其他业界专家和读者都会到这个论坛上来探讨问题。 在 http://p2p.wrox.com 上,有许多不同的论坛,它们不仅有助于阅读本书,还有助于 开发自己的应用程序。要加入论坛,可以遵循下面的步骤: (1) 进入 p2p.wrox.com,单击 Register 链接。 (2) 阅读使用协议,并单击 Agree 按钮。 (3) 填写加入该论坛所需要的信息和自己希望提供的其他信息,并单击 Submit 按钮。 (4) 你会收到一封电子邮件,其中的信息描述了如何验证账户和完成加入过程。 不加入 P2P 也可以阅读论坛上的消息,但要张贴自己的消息,就必须加入该论坛。 加入论坛后,就可以张贴新消息,回复其他用户张贴的消息。可以随时在 Web 上阅读 消息。如果要让该网站给自己发送特定论坛中的消息,可以单击论坛列表中该论坛名旁边 的 Subscribe to this Forum 图标。 关于使用 Wrox P2P 的更多信息,可阅读 P2P FAQ,了解论坛软件的工作情况以及 P2P 和 Wrox 图书的许多常见问题的解答。要阅读 FAQ,可以在任意 P2P 页面上单击 FAQ 链接。 目 录 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 第 1 章 数据驱动应用程序介绍..................................................................................................... 3 1.1 创建一个简单的数据驱动应用程序.................................................................................4 1.1.1 创建项目 ....................................................................................................................4 1.1.2 添加 UITableView.......................................................................................................6 1.1.3 获取数据 ....................................................................................................................9 1.1.4 实现数据模型类 .........................................................................................................9 1.1.5 显示数据 ................................................................................................................. 12 1.2 深入研究............................................................................................................................ 17 1.2.1 设计模式 ................................................................................................................. 17 1.2.2 读取文本文件.......................................................................................................... 18 1.3 前往下一章........................................................................................................................ 18 第 2 章 iOS数据库:SQLite.......................................................................................................19 2.1 什么是 SQLite................................................................................................................... 20 2.1.1 SQLite 库................................................................................................................. 20 2.1.2 SQLite 和 Core Data ................................................................................................. 20 2.2 创建一个简单的数据库................................................................................................... 21 2.2.1 设计数据库.............................................................................................................. 22 2.2.2 创建数据库.............................................................................................................. 24 2.2.3 填充数据库.............................................................................................................. 26 2.2.4 可视化 SQLite 数据库的工具................................................................................... 30 2.3 连接到数据库 ................................................................................................................... 32 2.3.1 启动项目 ................................................................................................................. 33 2.3.2 模型类..................................................................................................................... 35 2.3.3 DBAccess 类............................................................................................................ 37 2.3.4 参数化查询.............................................................................................................. 45 2.3.5 写入数据库.............................................................................................................. 46 2.3.6 显示目录 ................................................................................................................. 47 2.3.7 查看产品详情.......................................................................................................... 49 2.4 前往下一章........................................................................................................................ 53 iOS 数据库应用高级编程(第 2 版) X 第 3 章 使用 UITableView 显示数据..........................................................................................55 3.1 定制表视图........................................................................................................................ 55 3.1.1 表视图单元格样式................................................................................................... 56 3.1.2 将子视图添加到 contentView ................................................................................... 59 3.1.3 子类化 UITableViewCell .......................................................................................... 63 3.2 实现区段和索引 ............................................................................................................... 69 3.3 实现搜索............................................................................................................................ 76 3.4 优化表视图性能 ............................................................................................................... 81 3.4.1 重新使用现有的单元格............................................................................................ 82 3.4.2 不透明的子视图 ...................................................................................................... 83 3.4.3 使用 drawRect 自定义绘制单元格............................................................................ 84 3.4.4 配件视图的用户界面约定........................................................................................ 84 3.5 前往下一章........................................................................................................................ 85 第 4 章 用户界面元素 ...................................................................................................................87 4.1 使用故事板创建界面....................................................................................................... 87 4.1.1 开始创建故事板示例应用程序................................................................................. 89 4.1.2 向故事板中添加 Sub-detail 屏幕............................................................................... 90 4.1.3 向故事板中添加模态屏幕........................................................................................ 90 4.2 使用分隔视图控制器显示主/从数据............................................................................. 91 4.2.1 UISplitViewController 简介 ...................................................................................... 92 4.2.2 UISplitViewControllerDelegate 协议.......................................................................... 93 4.2.3 开始实现分隔视图示例应用程序 ............................................................................. 94 4.2.4 创建 Detail 界面....................................................................................................... 96 4.2.5 使用主/从视图添加调查........................................................................................... 97 4.3 在弹出窗口中显示数据................................................................................................. 103 4.3.1 创建 InfoViewController......................................................................................... 104 4.3.2 显示 UIPopoverController....................................................................................... 105 4.4 手势识别.......................................................................................................................... 106 4.4.1 UIGestureRecognizer 类.......................................................................................... 106 4.4.2 使用手势识别器 .................................................................................................... 107 4.5 文件共享支持 ..................................................................................................................111 4.5.1 在示例应用程序中启用文件共享 ........................................................................... 111 4.5.2 序列化调查数据数组 ............................................................................................. 112 4.5.3 反序列化并加载调查数据数组............................................................................... 113 4.5.4 共享数据 ............................................................................................................... 114 4.6 前往下一章.......................................................................................................................116 目 录 XI 第Ⅱ部分 使用 Core Data 管理数据 第 5 章 Core Data 介绍..............................................................................................................119 5.1 Core Data 基础................................................................................................................ 120 5.2 Core Data 架构................................................................................................................ 120 5.2.1 Core Data 栈 .......................................................................................................... 120 5.2.2 SQLite 和 Core Data ............................................................................................... 122 5.3 Core Data 和 iCloud........................................................................................................ 123 5.4 数据保护.......................................................................................................................... 123 5.5 一个简单的使用 Core Data 实现的任务管理器......................................................... 124 5.5.1 创建项目 ............................................................................................................... 125 5.5.2 检查模板代码........................................................................................................ 125 5.5.3 修改模板代码........................................................................................................ 137 5.6 前往下一章...................................................................................................................... 142 第 6 章 在 Xcode 中对数据建模 ...............................................................................................143 6.1 模型化数据...................................................................................................................... 143 6.1.1 定义实体和它们的特性.......................................................................................... 145 6.1.2 添加实体间的关系................................................................................................. 149 6.1.3 创建获取属性和获取请求模板............................................................................... 151 6.2 创建定制 NSManagedObject 子类 ............................................................................... 154 6.2.1 实现验证规则........................................................................................................ 156 6.2.2 实现默认值............................................................................................................ 157 6.3 创建任务模型 ................................................................................................................. 157 6.4 前往下一章...................................................................................................................... 159 第 7 章 创建 Core Data 应用程序 ............................................................................................161 7.1 任务应用程序架构......................................................................................................... 161 7.1.1 数据模型 ............................................................................................................... 162 7.1.2 类模型................................................................................................................... 162 7.1.3 用户界面 ............................................................................................................... 163 7.2 编写应用程序代码......................................................................................................... 164 7.3 MasterViewController 和基本用户界面....................................................................... 164 7.4 产生托管对象子类......................................................................................................... 168 7.5 添加和查看任务 ............................................................................................................. 169 7.5.1 创建 ViewTaskController........................................................................................ 169 7.5.2 修改 MasterViewController..................................................................................... 173 7.6 创建编辑控制器 ............................................................................................................. 176 7.6.1 使用 EditTextViewController 编辑文本................................................................... 176 7.6.2 使用 EditPriorityViewController 设置优先级 ........................................................... 181 iOS 数据库应用高级编程(第 2 版) XII 7.6.3 使用 EditLocationViewController 添加和编辑位置 .................................................. 185 7.6.4 使用 EditDateController 修改日期........................................................................... 191 7.6.5 完成编辑控制器 .................................................................................................... 196 7.7 在 MasterViewController 中显示结果.......................................................................... 198 7.7.1 使用 NSSortDescriptor 排序结果 ............................................................................ 199 7.7.2 使用 NSPredicate 过滤结果 .................................................................................... 200 7.8 使用 NSFetchedResultsController 创建分组表........................................................... 201 7.9 实现定制托管对象......................................................................................................... 206 7.9.1 编写动态属性代码................................................................................................. 206 7.9.2 运行时默认值........................................................................................................ 207 7.9.3 验证单个字段........................................................................................................ 208 7.9.4 多字段验证............................................................................................................ 209 7.10 前往下一章 ....................................................................................................................211 第 8 章 和 Core Data 相关的 Cocoa 特性..............................................................................213 8.1 键-值编码 ........................................................................................................................ 213 8.1.1 键和键路径............................................................................................................ 214 8.1.2 使用键设置值........................................................................................................ 216 8.1.3 集合操作符............................................................................................................ 216 8.1.4 使用 KVC 时额外要考虑的事 ................................................................................ 217 8.2 键-值观察 ........................................................................................................................ 218 8.2.1 观察对象的改变 .................................................................................................... 218 8.2.2 自动和手动实现 KVO............................................................................................ 219 8.2.3 键-值观察示例....................................................................................................... 219 8.3 使用 NSPredicate ............................................................................................................ 225 8.3.1 创建谓词 ............................................................................................................... 225 8.3.2 使用谓词 ............................................................................................................... 228 8.4 排序描述符...................................................................................................................... 228 8.5 前往下一章...................................................................................................................... 229 第 9 章 Core Data 迁移和性能 .................................................................................................231 9.1 模型版本控制和架构迁移 ............................................................................................ 231 9.1.1 模型版本控制........................................................................................................ 233 9.1.2 轻量迁移 ............................................................................................................... 235 9.1.3 生成映射模型........................................................................................................ 237 9.2 线程安全与 Core Data ................................................................................................... 241 9.2.1 线程设计 ............................................................................................................... 241 9.2.2 线程和 Core Data ................................................................................................... 242 9.2.3 线程和 NSOperation............................................................................................... 242 9.2.4 Core Data 线程处理示例 ........................................................................................ 243 目 录 XIII 9.3 Core Data 性能................................................................................................................ 251 9.3.1 故障 ...................................................................................................................... 251 9.3.2 数据存储类型........................................................................................................ 252 9.3.3 存储二进制数据 .................................................................................................... 252 9.3.4 实体继承 ............................................................................................................... 253 9.3.5 运行时性能............................................................................................................ 254 9.3.6 使用获取结果控制器管理变化............................................................................... 254 9.4 使用 Instruments 进行性能分析 ................................................................................... 257 9.4.1 启动 Instruments .................................................................................................... 258 9.4.2 Instruments 界面 .................................................................................................... 258 9.4.3 Core Data 仪表....................................................................................................... 259 9.5 前往下一章...................................................................................................................... 260 第Ⅲ部分 使用 Web 服务集成应用程序 第 10 章 在 iPhone 上使用 XML...............................................................................................263 10.1 iOS SDK 和 Web........................................................................................................... 263 10.1.1 Web 应用程序架构 .............................................................................................. 264 10.1.2 同步数据获取 ...................................................................................................... 264 10.1.3 URL 加载系统 ..................................................................................................... 265 10.1.4 Web 访问示例...................................................................................................... 266 10.1.5 从服务器请求数据............................................................................................... 268 10.2 XML 和 iPhone SDK.................................................................................................... 275 10.2.1 XML 简要概述 .................................................................................................... 275 10.2.2 使用 NSXML 解析器解析 XML ........................................................................... 276 10.2.3 扩展示例,解析 XML.......................................................................................... 277 10.2.4 使用 libxml 生成 XML ......................................................................................... 283 10.2.5 XML 生成示例 .................................................................................................... 284 10.3 前往下一章 ................................................................................................................... 290 第 11 章 使用 Web 服务进行集成 ............................................................................................291 11.1 网络应用程序架构....................................................................................................... 291 11.1.1 两层架构 ............................................................................................................. 292 11.1.2 三层架构(n 层)..................................................................................................... 293 11.1.3 应用程序通信 ...................................................................................................... 294 11.2 Web 服务介绍 ............................................................................................................... 294 11.2.1 SOAP 消息 .......................................................................................................... 295 11.2.2 REST 协议........................................................................................................... 297 11.3 示例 1:基于位置的搜索............................................................................................ 297 11.3.1 开始 .................................................................................................................... 298 iOS 数据库应用高级编程(第 2 版) XIV 11.3.2 创建界面 ............................................................................................................. 299 11.3.3 Core Location ....................................................................................................... 299 11.3.4 本地搜索 API....................................................................................................... 302 11.3.5 使用搜索栏.......................................................................................................... 304 11.3.6 处理 Web 服务响应.............................................................................................. 307 11.4 示例 2:内容分析 ........................................................................................................ 318 11.4.1 开始 .................................................................................................................... 319 11.4.2 创建用户界面 ...................................................................................................... 320 11.4.3 实现 POST 调用................................................................................................... 321 11.4.4 接收 XML 响应.................................................................................................... 324 11.4.5 解析响应 XML .................................................................................................... 325 11.4.6 完成 .................................................................................................................... 327 11.5 前往下一章.................................................................................................................... 327 附录 A 应用程序故障诊断工具 .................................................................................................329 A.1 Instruments...................................................................................................................... 329 A.1.1 启动 Instruments.................................................................................................... 330 A.1.2 跟踪文档 .............................................................................................................. 331 A.1.3 Objective-C 内存管理............................................................................................ 332 A.1.4 内存泄漏示例应用程序......................................................................................... 334 A.1.5 在 Instruments 中分析内存泄漏 ............................................................................. 335 A.2 静态分析器..................................................................................................................... 338 第Ⅰ部分 操作和显示iPhone和iPad上的数据 第 1 章:数据驱动应用程序介绍 第 2 章:iOS 数据库:SQLite 第 3 章:使用 UITableView 显示数据 第 4 章:用户界面元素 数据驱动应用程序介绍 本章包含的内容: ● 使用 Xcode 创建基于视图的应用程序 ● 创建一个简单的数据模型 ● 使用 UITableView 控件在表中整齐地显示数据 从 wrox.com 下载本章代码 在 www.wrox.com/remtitle.cgi?isbn=1118391845 页面的 Download Code 选项卡中提供本 章代码的下载。代码位于下载的压缩包中的 Chapter 1 目录,该目录是按照本章名称单独命 名的。 数据是大多数应用程序的骨干。这并不仅限于商业应用程序,游戏、图形编辑器和电 子表格都以某种形式使用和操作数据。iOS 应用程序的最令人兴奋的事情之一就是它们允 许你和你的客户可在任何地方获得数据。iOS 设备的移动性给开发人员提供了一个了不起 的平台,用来开发使用这些数据的应用程序。可以使用现有的数据来创建以新的方式使用 该数据的应用程序。在本书中,你将学习如何显示数据、创建和操作数据,并且通过互联 网发送和接收数据。 在本章中,你将学习如何创建一个简单的数据驱动应用程序。当然,这个应用程序不 能用于生产环境,它主要用来让你熟悉一些工具,这些工具在创建数据驱动应用程序时将 会用到。在本章结束时,你将能使用 Xcode 创建基于视图的应用程序,该应用程序使用 UITableView 控件显示数据。你还会了解模型-视图-控制器(Model-View-Controller,MVC) 架构,它是许多 iOS 应用程序的基础。 1 第 章 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 4 1.1 创建一个简单的数据驱动应用程序 很多 iOS 应用程序都需要以某种形式来处理数据。通常将这些数据显示在表中。在本 节你将学习如何创建一个在表中显示数据的 iOS 应用程序。 1.1.1 创建项目 要创建 iOS 应用程序,需要苹果公司提供的 Xcode 开发环境。Xcode 是一个强大的集 成开发环境,具有现代 IDE 的所有特性。它集成了很多强大的特性,包括代码编辑、调试、 版本控制和软件分析。如果还没有安装 Xcode,则可以通过 Mac App Store 安装它,或者从 苹果开发人员网站的 https://developer.apple.com/xcode/index.php 处下载它。 首先,启动 Xcode 并选择 File | New | Project。将弹出一个显示各种类型应用程序的模 板的对话框,可通过这些模板来创建 iOS 或 Mac OS X 应用程序,如图 1-1 所示。 图 1-1 New Project 对话框 这里显示的每个选项提供开始开发应用程序所需的基本设置。iOS 模板默认被分为三 组:Application(应用程序)、Framework & Library(框架和库)以及 Other(其他)。 Application 组包含下列模板: ● Master-Detail Application:该模板为主从(master-detail)应用程序提供一个起点。它 提供一个使用导航控制器配置的用户界面,用来显示项的列表和在 iPad 上的分隔 视图。例如,Contacts(联系人)应用程序就是一个主从应用程序。联系人列表是主表, 当单击一个联系人时看到的单独的联系人信息是从表。 ● OpenGL Game:该模板提供基于 OpenGL ES 的游戏的起点。它提供一个用来渲染 OpenGL ES 场景的视图和一个用来在该视图中实现动画的时钟。OpenGL 是一门图 形语言,可用于创建游戏和其他图形相关的应用程序。在本书中将不会用到 OpenGL。 第 1 章 数据驱动应用程序介绍 5 ● Page-Based Application:该模板提供一个基于页面的应用程序的起点,该应用程 序使用一个页面视图控制器。可使用此模板创建诸如 iBooks 这样的包含页面布局 和翻页动画的应用程序。 ● Single View Application:该模板提供使用单一视图的应用程序的起点。它提供一 个视图控制器来管理视图,并且还提供一个包含该视图的故事板或 nib 文件。例如, 计算器应用程序就是一个单一视图应用程序。 ● Tabbed Application:该模板为使用标签栏的应用程序提供一个起点。它提供一个 使用标签栏控制器配置的用户界面,还提供这些标签栏项的视图控制器。时钟应用 程序就是一个标签式应用程序。底部的标签可用来在世界时钟、闹钟、秒表和时钟 之间进行切换。 ● Utility Application:该模板为实用应用程序提供一个起点,该应用程序具有一个主 视图和一个备用视图。对于 iPhone,它配置一个 Info 按钮,从主视图切换到备用视 图。对于 iPad,它配置一个 Info 栏按钮,在一个弹出框中显示备用视图。天气应用 程序就是一个实用应用程序。它提供一个简单的界面和一个信息按钮用来在界面间 切换以允许高级定制。 ● Empty Application:该模板提供任何应用程序的起点。它只提供一个应用程序委托 和一个窗口。如果想要完全从头创建一个没有非常多的模板代码的应用程序,则可 以使用该模板。 Framework & Library 模板集合只包含一个模板:Cocoa Touch Static Library。可以使用 该模板创建链接 Foundation 框架的代码的静态库。在本书中将不会用到该模板。然而,对 于创建在多个项目间共享的代码库来说该模板是非常有用的。 Other 模板集合也只有一个模板:Empty。Empty 模板只是一个空项目,等待你用代码 来填充它。在本书中将不会用到该模板,但如果想要开始一个新项目并且现有模板都不合 适,那么会用到该模板。 对于本章的示例应用程序来说,可以直接使用 Single View Application 模板。 从对话框中选择 Single View Application 模板并单击 Next 按钮。将项目名称设置为 SampleTestProject,保持 Company Identifier 默认设置不变,并在 Devices 下拉列表中选择 iPhone。选中标注为 Use Automatic Reference Counting 的复选框,并确保其他两个复选框 没被选中。单击 Next 按钮。选择一个位置来保存项目,并单击 Create 按钮。Xcode 将创建 项目并显示项目窗口。现在已经准备就绪了! Xcode 当前显示的是项目窗口,如图 1-2 所示。在项目窗口中,可在左侧窗格中看到 导航器。导航器简化了在项目的各个部分之间的导航。在导航器区域顶部的选择器栏允许 你选择想要导航的特定区域。这里可以使用 7 种不同的导航器。 导航器选择器栏的第一个图标表示 Project Navigator(项目导航器)。使用项目导航器可 查看项目文件。可以使用文件夹组织文件并且使用项目导航器深入浏览这些文件夹以到达 想要编辑的文件。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 6 图 1-2 Xcode 项目窗口 在项目导航器中选择的项将显示在 Editor(编辑器)区域中。选择一个源代码文件(.m 后 缀)将使该文件在编辑器区域中打开。如果选择一个用户界面文件(.xib 后缀),Interface Builder 将在编辑器区域中打开,从而可以处理用户界面文件。双击一个代码文件将在新的 标签窗口中打开它,这将使代码更容易处理。如果想要双击一个代码文件来在新窗口中打 开该文件,可以在 Xcode | Preferences 对话框的 General 选项卡中改变此行为。 通过选择项目导航器顶端的项目节点来在编辑器区域打开项目设置,用来改变项目的 各种配置。 除了项目导航器,在左侧的窗格中还有 Symbol(符号)、Search(搜索)、Issue(问题)、 Debug(调试)、Breakpoint(断点)和 Log(日志)导航器。可使用这些导航器打开项目的不同视 图。可以随意单击文件夹、文件或导航器的标签图标来研究 Xcode 如何组织项目。可 以随时添加额外的文件夹来帮助自己组织代码和其他的项目资源,比如图像、音频和文 本文件。 1.1.2 添加 UITableView 在 iOS 中最常用的用来显示数据的控件是 UITableView。顾名思义,UITableView 是一 个在表中显示数据的视图。在 iOS 的联系人应用程序中可以看到 UITableView 的用途。该 第 1 章 数据驱动应用程序介绍 7 应用程序在一个 UITableView 控件中显示联系人列表。在第 3 章中将深入学习 UITableView 控件。 通常,当开发 iOS 应用程序界面时,会用到 Interface Builder。该工具对于交互式布局、 设计和开发界面非常有用。然而,本章的焦点不是设计一个漂亮的界面,而是在 iOS 上显 示数据。所以这里不使用 Interface Builder 设计具有表视图的屏幕,而是以编程的方式创建 和显示它。 要创建这个表视图,需要修改示例项目的主视图控制器。 1. 模型-视图-控制器架构 在继续介绍示例前,需要先理解用来创建大多数 iOS 应用程序的基本架构:模型-视图- 控制器。该架构包含三部分,如图 1-3 所示。 你可能已经猜到,它们分别是模型、视图和 控制器。 模型是表示数据的类或类集合。仅需要 设计包含数据的模型类和操作数据的功能。 模型类不需要知道如何显示它所包含的数 据。实际上,可以把模型类视作除了它自己 的数据外不知道其他任何事情的类。当模型 中的数据状态改变时,模型可以通知任何对 其感兴趣的类,向侦听者报告状态改变。作 为一种选择,控制器类可以观察模型并对其 改变做出响应。 通常,模型对象应该封装所有数据。封装是一个重要的面向对象设计原则。封装的思 想是避免其他对象改变对象的数据。要有效地封装数据,应该实现接口方法或属性,这些 接口方法和属性用来暴露模型类的数据。类不应该让它们的数据以公有变量的方式提供。 面向对象编程的完整讨论超出了本书的范围,但有很多好的资源可用来学习 OOP(面 向对象编程)。在本章结尾的 1.2 节“深入研究”中提供了一些相关资源。在模型对象中封 装数据将产生高质量的、清晰的面向对象设计,从而可以容易地扩展和维护该设计。 MVC架构的视图部分是用户界面。图形、小部件、表格和文本框向用户显示封装在模 型中的数据。用户通过视图和模型交互。视图类应该只包含向用户显示模型数据的代码。 在很多iOS应用程序中,不需要为视图编写任何代码。很多时候,设计和创建视图的工作 可以全部在Interface Builder中进行。 控制器是粘合模型和视图的胶水。控制器包含告知视图显示什么的所有逻辑。它还负 责告知模型如何基于用户的输入而改变。iOS 应用程序中的大多数代码都包含在控制器 类中。 大致总结一下,模型是应用程序数据,视图是用户界面,控制器是绑定视图和模型的 控制器 模型 视图 图 1-3 模型-视图-控制器架构 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 8 业务逻辑代码。 在该示例应用程序中,将创建一个充当数据模型的类。Xcode 会为该应用程序创建一 个默认控制器作为 Single View Application 代码模板的一部分,并且将添加一个表视图作为 视图,以便看到在模型中包含的数据。然后将编写控制器代码以绑定视图和模型。 2. 以编程的方式添加表视图 现在你对 MVC 架构有了基本理解,现在继续前进,为应用程序添加表视图。单击“项 目导航器”中的 SampleTableProject 文件夹左侧的三角符号来打开该文件夹。选择该文件 夹中的 ViewController.m 文件使其代码显示到代码窗口中。 因为不打算使用 Interface Builder 创建该应用程序的界面,所以需要重写 UIViewController 的 loadView 方法来创建视图。这就需要在 loadView 方法中编写代码以便当视图控制器加 载视图时,这些代码可以创建 UITableView 类的实例并将它的 view 属性设置到新创建的视 图。将下列代码添加到 ViewController 类的实现中: - (void)loadView { CGRect cgRct = CGRectMake(0, 20, 320, 460); UITableView * myTable = [[UITableView alloc] initWithFrame:cgRct]; self.view = myTable; } 第一行代码创建了一个 CGRect,它是一个 Core Graphics 架构,用来指定表视图的大 小和位置。这里将其原点位置设置为(0,20),并定义其宽度为 320 点、高度为 460 点。该表 视图将覆盖整个屏幕,但其到屏幕顶部的距离为 20 点,位于状态栏之下。 下一行代码创建一个 UITableView 实例并使用前面那行代码定义的大小初始化它。 仅仅创建表视图的实例并不能使其显示到视 图中。还需要将视图控制器的 view 属性设置到刚 创建的表视图来告知视图控制器。 现在可以直接单击 Xcode 窗口左侧顶部工具 栏中的 Run 图标来运行该项目。你将看到应用程 序成功编译并在 iOS 模拟器中启动。你还会在模 拟器中看到多行灰线,如图 1-4 中所示。这就是 创建的表视图!这些灰线将数据分成多行。因为 到现在为止还没有用来显示的表视图的数据,所 以该表是空的。然而,可以单击模拟器并拖动这 个表视图。此时,应该能看到当上下拖动时,这 些线也会随之移动并且当释放鼠标按键时这些线 会回到原来的位置。 图 1-4 运行具有表视图的应用程序 第 1 章 数据驱动应用程序介绍 9 1.1.3 获取数据 不能够显示数据的表是无用的。为了让第一个示例简单点,下面将创建一个非常简单 的数据模型。该数据模型是一个包含将显示到表中的名称列表的类。模型类包括一个存放 数据列表的数组,一个返回指定索引对应的名称的方法,以及一个返回模型中项总数的 方法。 要在项目中创建一个新类,需要首先单击 Xcode 中的项目导航器中的 SampleTableProject 文件夹,然后选择 File | New | File。你将看到 New File 对话框,其中显示了可以在 Xcode 项目中创建的所有文件类型。 在该对话框的左侧窗格中选择 Cocoa Touch,再选择 Objective-C Class 作为想要创建的 文件类型,并单击 Next 按钮。在下一个屏幕中,在 Class 文本框中输入 DataModel 来将类 命名为 DataModel。接着在 Subclass Of 后面的下拉框中选择 NSObject。该模板允许创建 NSObject、UIView、UIViewController、UITableViewController 或 UITableViewCell 的子类。 在此情况中,想要创建的是 NSObject 的子类,所以保持 NSObject 被选中。单击 Next 按钮 进入到下一个屏幕。 在最后一个对话框中,可以告诉 Xcode 将文件放到哪里。该对话框的选项允许指定文 件位置、用来包含新类的组以及用来编译文件的构建目标。保持这些选项的默认值不变并 单击 Create 按钮让 Xcode 产生新类文件。 1.1.4 实现数据模型类 因为模型类需要向表视图提供数据,所以其需要一个返回请求的数据的方法。因此, 需要创建一个名为 getNameAtIndex 的接口方法,它将从模型中返回和传入索引对应的 名称。 在 Xcode 的项目导航器中选择 DataModel.h 头文件以显示该文件内容。在接口定义下 面,添加下面这一行代码,它用于声明 getNameAtIndex 接口方法: -(NSString*) getNameAtIndex:(int) index; 还需要一个接口方法用来告知该类的用户你将返回多少行。所以,向该接口中添加另 外一个名为 getRowCount 的方法。在 getNameAtIndex 声明的下面,添加 getRowCount 声明: -(int) getRowCount; 目前头文件看起来应该如下所示: #import @interface DataModel :NSObject -(NSString*) getNameAtIndex:(int) index; -(int) getRowCount; @end 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 10 现在切换到数据模型的实现文件DataModel.m并实现新的方法。在Xcode中可使用组合 键Ctrl+Cmd+Up快速在头文件和实现文件间切换。还可以使用Assistant Editor(辅助编辑器) 并列显示源文件和头文件。Xcode窗口的右上角编辑器菜单栏的第二个图标即为Assistant Editor。该图标像一个燕尾服。 注意: 学习 Xcode 的键盘快捷键对你很有帮助。学习这些快捷键所花费的时间可从节省的时 间中得到弥补。 在实现文件的#import 语句下面,添加一个局部变量来存放数据列表。通常,应用程序 的数据来自数据库或其他数据源。为了让示例尽可能简单,这里使用一个 NSArray 作为数 据源。将下面的代码行添加到 DataModel.m 文件中的#import 语句之下: NSArray* myData; 现在,在@implementation 块中添加 getNameAtIndex 方法的实现。在 DataModel.m 文 件中将下列代码添加到@implementation 和@end 这两个标记之间: -(NSString*) getNameAtIndex:(int) index { } 在开始实现该方法前,需要先初始化数据存储,即 myData 数组。对于本例,将在类 的初始化器中初始化它。在方法存根 getNameAtIndex 之上,添加下列代码来初始化类: -(id)init { if (self = [super init]) { // Initialization code myData = [[NSArrayalloc] initWithObjects:@"Albert", @"Bill", @"Chuck", @"Dave", @"Ethan", @"Franny", @"George", @"Holly", @"Inez", nil]; } return self; } 代码的第一行调用父类的 init 函数。在你实现的任何子类中都应该调用父类的 init 方 法。这样可以确保在进行子类中的任何工作前父类的特性都被构造好了。 下一行代码为数组分配内存并使用一个名称列表填充它。 最后一行代码返回此类的实例。 现在已经完成了数据的初始化,下面可以实现获取数据的方法了。在本例中这是相当 简单的。只需要返回数组中指定位置处的字符串,例如: -(NSString*) getNameAtIndex:(int) index 第 1 章 数据驱动应用程序介绍 11 { return (NSString*)[myData objectAtIndex:index]; } 该行代码仅仅在数组中查找指定索引处的对象并把它转换为 NSString *类型。这种转 换是安全的,因为在前面已经手工填充了这些数据并能保证指定索引处的对象是一个 NSString。 注意: 为了简化这个示例,这里去掉了对指定索引的范围检查,在生产应用程序中应该添加 上它。 要实现 getRowCount,只需要返回本地数组的元素数量,如下所示: -(int) getRowCount { return [myData count]; } 此时如果从菜单中选择 Product | Build 或按下 Cmd+B 组合键构建当前项目,代码的编 译和链接将没有任何错误或警告。如果存在错误或警告,可返回并检查代码,确保所有代 码都被正确输入。 我是尽早和经常编译的强烈支持者。通常,在写完每个方法或有点难度的代码后,都 要尝试进行构建。这是一个好习惯,因为如果在上次成功编译以来添加的代码量很小,那 么可以非常容易地缩小编译错误范围。这个方法还限制了收到的错误或警告的数量。如果 你写了 2000 行代码后才开始编译,你很可能会被大量的错误(或至少是警告)淹没。有时查 找这些错误的来源也很困难,因为编译器和链接器给出的错误往往有点含糊。 最终完成的数据模型类应如下所示: #import "DataModel.h" NSArray* myData; @implementation DataModel -(id)init { if (self = [super init]) { // Initialization code myData = [[NSArray alloc] initWithObjects:@"Albert", @"Bill", @"Chuck", @"Dave", @"Ethan", @"Franny", @"George", @"Holly", @"Inez", nil]; } return self; } 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 12 -(NSString*) getNameAtIndex:(int) index { return (NSString*)[myData objectAtIndex:index]; } -(int) getRowCount { return [myData count]; } @end 1.1.5 显示数据 现在已经完成了视图和模型,你需要使用控制器将它们关联起来。对于用来显示数据 的表视图来说,它需要知道数据是什么以及如何显示它。为此,UITableView 对象需要一 个委托和一个数据源。数据源在模型和表视图间协调数据。委托控制表视图的外观和行为。 为了确保你正确地实现了该委托,它必须实现 UITableViewDelegate 协议。同样,数据源也 必须实现 UITableViewDataSource 协议。 1. 协议 如果你熟悉 Java 或 C++,那么也应该熟悉协议。Java 接口和 C++纯虚类与协议是相同 的。协议只是调用者和实现者之间的正式契约。协议的定义表明了实现该协议的类必须实 现哪些方法。协议还可以包含可选的方法。 声称表视图的委托必须实现 UITableViewDelegate 协议意味着你同意一个契约。该契约 表明你将实现 UITableViewDelegate 协议中指定的必须实现的方法。同样,作为表视图的数 据源的类必须实现 UITableViewDataSource 协议中指定的必须实现的方法。这些说法听起 来可能有些混乱,但如果你继续学习这个示例,它们就会变得清晰起来。 为了保持该例尽可能简单并避免引入更多的类,这里让ViewController作为表视图的委托 和数据源。为此,需要在ViewController中实现UITableViewDelegate和UITableViewDataSource 协议。需要在头文件中声明ViewController类实现这些协议。在ViewController.h头文件中修 改@interface所在行,将计划实现的协议添加到接口名称和继承层次结构之后的尖括号中, 如下所示: @interface ViewController :UIViewController 如果现在编译项目,会出现一些警告。单击导航窗格顶部的 Issue Navigator 图标或使 用快捷键 Cmd+4 可转到 Issue Navigator 图标。屏幕看起来应该如图 1-5 中所示。 第 1 章 数据驱动应用程序介绍 13 图 1-5 使用 Issue Navigator 可以看到这些警告与 ViewController.m 的编译有关。具体来说,应能看到警告“Semantic Issue incomplete implementation”(语义问题不完整的实现)和“Semantic Issue Method in protocol not implemented”(语义问题协议中的方法没有实现)。 这些警告的意义明确,即还没有实现声称要实现的协议。实际上,如果在 Issue Navigator 中展开这些问题,可以使用该导航器看到必须实现的但还没有实现的方法。展开第一个问 题“ Semantic Issue Method in protocol not implemented”,将看到两项。单击标注为“Method declared here”(方法在此处声明)的项,在编辑器窗格中将显示该协议的定义,其中没有实 现的方法将被高亮显示。目前,该方法是 tableView:numberOfRowsInSection:。如果单击标 注为“Required for direct or indirect protocol ‘UITableViewDataSource,’”(直接或间接要求协 议‘UITableViewDataSource’)的项,编辑器窗口将返回到声明将实现 UITableViewDataSource 协议的源代码。 如果不清楚哪个方法对于实现一个协议是必需的,可以快速构建一下,编译器会告诉 你到底哪个或哪些方法是还没有实现的。 2. 实现 UITableViewDataSource 协议 通过实现 UITableViewDataSource 协议,可以去掉这些警告,并且向着能工作的应用 程序又迈进了一步。 因为要在 ViewController 类中使用 DataModel 类,所以需要导入 DataModel.h 头文件。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 14 在 ViewController.h 头文件中,将下列#import 语句添加到#import 语句下面: #import "DataModel.h" 现在已经导入了 DataModel 类,下一步需要创建 DataModel 类型的实例变量。在 ViewController.m 实现文件的@implementation 关键字下面添加下列声明: DataModel* model; 要实际创建模型类的实例,在 loadView 方法的开始处添加下列代码: model=[[DataModelalloc] init]; 现在已经有了一个初始化好的模型,可以实现 UITableViewDataSource 协议中必须实 现的方法了。从编译器警告中可以看到需要实现的方法是 cellForRowAtIndexPath 和 numberOfRowsInSection。 numberOfRowsInSection 方法告知表视图在当前区段需要显示多少行。表视图可以分为 多个区段。在联系人应用程序中,每个区段之前是一个字母表中的字母。在当前示例中, 只有一个区段,但在第 3 章中可看到如何实现多个区段。 要实现 numberOfRowsInSection 方法,需要通过调用模型的 getRowCount 方法来获得 数据源包含的行数: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [model getRowCount]; } 现在如果查看 Issue Navigator,可看到没有实现 numberOfRowsInSection 的警告已经不 存在了。 cellForRowAtIndexPath 方法返回实际的 UITableViewCell 对象,它用于在表视图中显 示数据。无论何时当表视图需要显示一个单元格时它都会调用这个方法。NSIndexPath 参 数标识需要的单元格。所以,你要做的是编写一个基于表视图要求的行返回正确的 UITableViewCell 的方法。例如: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ; } 第 1 章 数据驱动应用程序介绍 15 NSUInteger row = [indexPath row]; cell.textLabel.text = [model getNameAtIndex:row]; return cell; } 前面几行代码返回一个有效的 UITableViewCell 对象。在此不会深入到具体细节中, 因为在第 3 章中会详细介绍它们,该章专门针对 UITableView。目前,简单地说就是为了 性能目的而想要尽可能地重用表视图单元格,而这段代码就是做这个工作的。 最后几行代码获得调用者感兴趣的行,从模型中查找对应该行的数据,将单元格的文 本设置为模型中的名称,并返回 UITableViewCell。 这些就是全部内容。现在应该可以成功地构建没有错误或警告的项目了。 3. 委托 总而言之,在设计 iOS SDK 和 Cocoa 库时,苹果公司的工程师频繁使用了常见的设计 模式。目前你已经看到了在设计一个应用程序时如何使用 MVC 模式。另外,一个即将看 到的完全贯穿 Cocoa 和 Cocoa touch 框架的模式是委托。 在委托模式中,一个对象看起来做了某些工作;然而,它可以把这个工作委托给其他 类。例如,如果你的老板要求你完成某些工作,而你将它转交给另外的人来完成,那么你 的老板不关心是你还是其他人做这项工作,只要这项工作能完成即可。 当使用 iOS SDK 时,会遇到很多使用委托的实例,表视图就是这样一个实例。表视图 的委托实现了 UITableViewDelegate 协议。该协议提供管理行的选择、控制添加和删除单元 格、控制区段标题的配置以及其他各种控制数据显示的方法。 4. 结束语 最后一件与示例有关的事情是设置 UITableView 的 delegate 和 DataSource 属性。因为 已经在 ViewController 中实现了 delegte 和 DataSource 协议,所以将这两个属性设置为 self。 在 ViewController.m 文件的 loadView 方法中,添加下列代码配置表视图的数据源和 委托: [myTable setDelegate:self]; [myTable setDataSource:self]; ViewController.m 的最终代码应该看起来如代码清单 1-1 中所示。 代码清单 1-1:ViewController.m #import "ViewController.h" @interface ViewController () @end 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 16 @implementation ViewController DataModel* model; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [model getRowCount]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { staticNSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ; } NSUInteger row = [indexPath row]; cell.textLabel.text = [model getNameAtIndex:row]; return cell; } - (void)loadView { model = [[DataModelalloc] init]; CGRectcgRct = CGRectMake(0, 20, 320, 460); UITableView * myTable = [[UITableViewalloc] initWithFrame:cgRct]; self.view = myTable; [myTablesetDelegate:self]; [myTablesetDataSource:self]; } - (void)viewDidLoad { [superviewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)viewDidUnload { [superviewDidUnload]; // Release any retained subviews of the main view. } 第 1 章 数据驱动应用程序介绍 17 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); } @end 现在应该可以构建并在模拟器中运行应用程序 了。你应该能看到使用包含在 DataModel 类中的名称 填充的表,如图 1-6 所示。 恭喜!你已经成功创建了你的第一个数据驱动的 应用程序!如果你喜欢挑战,可以返回并修改 DataModel 来使用不同的数据源,比如文本文件。 1.2 深入研究 在本章你学习了如何创建一个使用 UITableView 控件显示数据的 iOS 应用程序。你还学习了一点和设计模式有关的知识——特别是 iOS 应 用程序开发中普遍存在的模型-视图-控制器模式。在第 2 章中,你会学习如何将 SQLite 数 据库用作数据源。接着,在第 3 章中,你将精通 UITableView 控件。在第 3 章的结尾,你 应该可以自己创建一个以数据为中心的 iOS 应用程序。 1.2.1 设计模式 如果你对编写可维护的、高质量的软件感兴趣,我强烈推荐Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides合著的Design Patterns:Elements of Reusable Object- Oriented Software(Addison-Wesley,1994)。该书是面向对象设计模式方面的经典。该书使用 UML模型、非常易读的模式说明及其实现,以及C++和Smalltalk代码示例阐明了每个模式。 我还想推荐 Cay S. Horstmann 撰写的 Object-Oriented Design and Patterns (Wiley,2005)。 尽管这本书的代码是用 Java 写的,但你会发现其中对模式的解释是非常出色的,将帮助你 进一步理解模式和它们在实现高质量软件方面的重要性。 即使你对这些书不感兴趣,就当是帮自己一个忙,用 Google 搜索“设计模式”,那么 将能搜索到大量和设计模式有关的文献,这是有原因的。试图重新发明轮子没有任何意义。 其他人已经发现了很多在软件设计过程中遇到的问题的解决方案。这些解决方案已经变成 了设计模式。这些设计模式的关键点是以每个人都能理解的形式向开发人员提供行之有效 的设计。这些模式经过了时间的证明并且提供了一个公共的词汇表,当你和其他开发人员 交流你的设计时该词汇表是非常有用的。 图 1-6 运行带有数据的表视图 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 18 1.2.2 读取文本文件 如果你想让这个简单的表视图应用程序更有趣,那么让其从文本文件中读取数据将很 容易实现。下面的代码片段显示了如何做到这一点: NSError *error; NSString *textFileContents = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"myTextFile" ofType:@"txt"] encoding:NSUTF8StringEncoding error:&error]; // If there are no results, something went wrong if (fileContents == nil) { // an error occurred NSLog(@"Error reading text file. %@", [error localizedFailureReason]); } NSArray *lines = [textFileContents componentsSeparatedByString:@"\n"]; NSLog(@"Number of lines in the file:%d", [lines count] ); 该代码读取 myTextFile.txt 文件的内容,该文件需要包含在代码包(code bundle)中。只 需要使用该名称创建一个文本文件并将它添加到 Xcode 项目中。 第一行声明了一个 error 对象,如果读取文本文件时出错,该对象将被返回。下一行代 码将文件的整个内容加载到一个字符串中。 接下来的一行代码是一个错误处理程序。如果调用 stringWithContentsOfFile 时返回 nil, 说明出现了错误。该错误使用 NSLog 函数输出到控制台。 再下面一行代码使用\n 将一个大的字符串分解为一个数组。文件中的每一行是 lines 数组中的一个元素。 最后一行输出从文件中读取的行数。 1.3 前往下一章 在本章中,你学习了如何创建一个简单的使用 NSArray 作为数据源的数据驱动应用程 序,还研究了在 Xcode 中创建项目时可用的项目选项。接下来你学习了模型-视图-控制器 架构以及表视图是如何符合此设计的。最后,你了解了协议和委托的重要概念。 第 2 章中,你将学习如何从 SQLite 数据库这个更健壮的数据源中获取数据。它是一个 包含在 iOS SDK 中的嵌入式数据库。学习如何使用这个数据库将使你能够创建丰富的、数 据驱动的 iOS 应用程序。 iOS 数据库:SQLite 本章包含的内容: ● 创建 SQLite 数据库 ● 连接应用程序到数据库并显示其数据 ● 在 SQLite 数据库上运行 SQL 语句以插入和选择数据 ● 创建数据库和查看主从关系的 iOS 应用程序 从 wrox.com 下载本章代码 在 www.wrox.com/remtitle.cgi?isbn=1118391845 页面中的 Download Code 选项卡中提供 了本章代码的下载。代码位于下载的压缩包中的 Chapter 2 目录,该目录是按照本章名称单 独命名的。 作为一名应用程序开发人员,你在存储 iOS 应用程序用到的数据时会有几个选择。可 以使用 plist 文件、XML 或纯文本。尽管在某些情况下这些解决方案是可接受的,但并非 所有方案都能为你的应用程序提供最好的性能。所有这些文件格式都不能快速查找指定数 据,也不能提供任何高效地对这些数据进行排序的方法。如果计划让应用程序处理大数据 集并且希望能够对该数据集进行查询或排序,则应该考虑使用 SQLite。 在第 1 章中,你已经学习了如何显示存储在一个简单数组中的小数据集。当你进一步 创建更复杂的应用程序时,你的数据集可能会变大。硬编码的数组将很可能无法满足更复 杂的应用程序的苛刻要求。你会发现随着 iOS 开发的进展,你需要一个比简单的数组更健 壮的数据存储方案。 在本章中,你将学习在背后支撑很多 iOS 应用程序的数据库引擎:SQLite。在本章结 束时,你将能够创建使用 SQLite 作为后台数据存储的应用程序。 要使用 SQLite,首先需要学习使用 Mac OS X 提供的命令行应用程序创建数据库。该 工具可以创建数据库架构、使用数据填充数据库以及执行查询。 2 第 章 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 20 接下来,将学习如何使用 iOS 应用程序部署数据库,在代码中连接数据库,以及显示 SQL 查询结果。 在本章结束时,你将知道如何创建一个完整功能的数据库应用程序以查看具有主从关 系的数据:在本例中是产品目录。 2.1 什么是 SQLite SQLite 是一个用 C 语言编写的开源库,它实现了一个自包含的 SQL 关系型数据库引 擎。可以使用 SQLite 存储大量关联数据。SQLite 的开发人员已经对其在如 iPhone 和 iPad 这样的嵌入式设备上的使用进行了优化。 尽管 Core Data 应用程序编程接口(API)也被设计用来在 iOS 上存储数据,但其主要目 的是持久化应用程序创建的对象。当预先加载具有大量数据的应用程序时,SQLite 的表现 比较突出,而 Core Data 擅长管理在设备上创建的数据。 2.1.1 SQLite 库 因为 SQLite 是完全自包含的 SQL 数据库引擎,所以所有的数据库数据都被存储在一 个单独的、跨平台的磁盘文件中。因为 SQLite 是自包含的,所以它需要几个外部库和操作 系统的一点支持。这是其非常适合如 iOS 这样的移动平台的主要原因。 苹果公司将 SQLite 用于 iOS 还有其他方面的原因,包括它占用空间小。SQLite 的大 小少于 300KB,该库非常小从而在内存有限的移动设备上可以有效地使用。另外,SQLite 不需要配置文件,没有安装过程,并且不需要管理员。只需要将数据库文件拖放到设备上, 然后在 iOS 项目中包含 SQLite 库,你就完成了准备工作。 因为 SQLite 实现了大部分的 SQL92 标准,所以如果已经了解了 SQL,那么使用 SQLite 数据库是很直观的。需要记住 SQL92 的某些特性目前 SQLite 还不支持。这些特性包括 RIGHT 和 FULL OUTER JOIN,以及对 ALTER TABLE、FOR EACH STATEMENT 触发器、 可写的视图、GRANT 和 REVOKE 权限的完整支持。关于未支持功能的详细信息,可查看 SQLite 网站 http://www.sqlite.org/omitted.html。 因为 SQLite 的接口用 C 语言编写,并且 Objective-C 是 C 的超集,所以可以非常容易 地将 SQLite 集成到基于 Objective-C 的 iOS 项目中。 2.1.2 SQLite 和 Core Data 当开始编写以数据为中心的 iOS 应用程序时,需要对架构做出重要决定。即应该使用 SQLite 还是使用 Core Data 来满足数据管理需求? 首先快速查看一下 Core Data 到底是什么。首先,Core Data 不是像 SQLite 这样的关系 型数据库。Core Data 是一个对象持久化框架。它的主要目的是提供给开发人员一个框架, 用来保存应用程序创建的对象。Core Data 提供一个 Xcode 内置的方便图形界面来将数据建 模为对象,接着可以使用一个丰富的 API 集在代码中操作这些对象。使用图形界面设计和 第 2 章 iOS数据库:SQLite 21 定义数据对象可以简化模型-视图-控制器(MVC)架构中模型部分的创建。 Core Data 可以使用 SQLite 以及其他存储类型作为其数据的后台存储。对于开发人员 这会导致一些混乱。常见的误解是既然 Core Data 可以使用 SQLite 存储数据,因此 Core Data 是关系型数据库。这种想法是错误的。如前所述,Core Data 不是一个关系型数据库的实现。 尽管 Core Data 在后台使用 SQLite 存储数据,但它不是以开发人员可直接访问的方式存储 数据的。实际上,不要尝试人工修改后台数据库结构或数据。应该只有 Core Data 框架能 够操作数据库结构及其数据。如果对此好奇,可以打开 SQLite 数据库进行查看,但修改数 据或数据库结构将可能使其无效,从而当使用 Core Data 访问它们时会导致问题。 尽管 Core Data 是处理在设备上创建的数据的首选框架,但对于 iOS 开发人员来说 SQLite 仍然是一个有用的工具。如果需要关系型数据库提供的功能,务必考虑直接使用 SQLite。然而,如果只需要持久化应用程序使用期间创建的对象,则应该考虑使用 Core Data。在本书的第Ⅱ部分将详细研究 Core Data 框架。 Core Data 是用来在设备上创建数据的推荐框架,但你可能会放弃 Core Data 并直接使 用 SQLite API。 如果你想要预加载设备上的大量数据,可能会选择使用 SQLite 数据库。以一个 GPS 导航应用程序为例。导航应用程序需要大量的数据,包括感兴趣的点和地图自身的数据。 一个好的架构设计是创建一个包含所有兴趣点(POI)和地图数据的 SQLite 数据库。接着可 以部署数据库和该应用程序,并使用 SQLite API 访问该数据库。 使用桌面工具可很容易地创建 SQLite 数据库。接下来可以使用相同的工具、脚本或桌 面应用程序将数据加载到数据库中。做完这些后,可以简单地将该数据库和应用程序一起 部署到设备上。 在本章中,将创建一个用于目录应用程序的数据库,该目录应用程序提供给移动的销 售人员使用。该目录需要和数据一起被预先加载,不是由设备自身填充,所以需要使用 SQLite 作为后台数据存储。 2.2 创建一个简单的数据库 在本节中,将创建用于销售目录应用程序的后台数据库。在开始设计数据库和应用程 序前,需要理解应用程序将做什么。在现实世界中,你将(或应该)得到一个详细的规范, 从而定义该应用程序应该做什么及其外观看起来应该是什么样子的。当然,该应用程序的 实现,或其应该如何工作是设计者和开发人员的责任。这里有一些对目录应用程序的简单 需求。 该应用程序的目的是显示公司的部件目录。每个部件都包括一个制造商、一个产品名 称、一些关于产品的详细信息、产品的价格、现有数量、原产地和一个产品图片。 应用程序应该从显示一个产品列表开始。单击一个产品应该进入到一个详情页面,用 来显示该产品的详细信息。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 22 在设计一个应用程序时,第一步在纸上画出用户界面原型和进行数据库设计经常是很 有帮助的。通常,界面自身可以有助于驱动出如何组织数据库中的数据的决定。我喜欢使 用 Omni Group(http://www.omnigroup.com/applications/OmniGraffle)开发的 OmniGraffle 进行 设计工作。目前它是非常易用的强大的 Mac 矢量图形应用程序,可以用来快速进行设计工 作。另外,该应用程序的输出足够用来呈现给管理者和其他可能不懂技术的利益相关者。 使用图像比使用文字能更容易地解释一个应用程序的设计。 我怀疑数据库专家正在那里拉扯自己的头发,因为用户界面和数据应该是完全解耦 的、并且应该独立地规范化数据是一个常识。然而,当开发运行在如 iPhone 或 iPad 这样 的嵌入式设备上的应用程序时,性能是一个重要问题。完全规范化的、没有重复的数据对 性能有负面影响。有时花费在执行一个复杂查询上的时间要高于花费在操作两个表中的相 同数据上的时间。我不是建议完全不标规范数据;只是在进行数据库设计的过程中要牢记 你将如何显示数据。 2.2.1 设计数据库 规范化是以某种方式分解数据的过程,它使得数据更容易查询。规范化帮助解决数据 库存储中的常见问题,比如数据的重复。例如,当创建数据库时,设计者可能想要在一个 表中存储所有数据,如图 2-1 所示。 标识 名称 制造商 详细信息 价格 现有数量 原产地 图像 产品 图 2-1 在表中存储数据 如果你不熟悉实体-关系图(ERD),那么其中的长方形表示一个实体或数据库中的表。 连接到实体的椭圆是该实体的特性或表的字段。所以,该图显示了一个表和作为该表的字 第 2 章 iOS数据库:SQLite 23 段的每个特性。 该数据库的设计问题是没有对它进行规范化。如果目录中多个产品的制造商是相同的 或多个产品是在一个国家制造的,那么会有数据重复的问题。在此情况下,数据可能看起 来如图 2-2 所示。 图 2-2 在单个表中的数据 可以看到在数据库中同一制造商有多个产品。另外,有多个产品在一个国家制造。该 设计是一个维护问题。如果数据录入员在第一项中输入 Spirit Industries 并在第三项中输入 Spit Industries,将会发生什么?该应用程序将显示两个不同的公司生产了这些产品,而实 际上 Spirit Industries 制造商生产了两种产品。这是一个数据完整性问题,可以通过规范化 数据来避免该问题。具体做法是从产品表中移除制造商和原产地并为这些字段创建新表, 从而在产品表中只需要引用相关表中的值即可。 此外,这个新的设计可以添加更多的制造商的详细信息,例如地址、联系人姓名等。 为了简单起见,在本例中并不这么做,但适当地规范化将使该类型尽可能地灵活。 新的设计看起来应如图 2-3 所示。 产品标识 名称 详细信息 价格 现有数量 图像 产品 制造商 具有 产地 制造商标识 名称 产地 产地标识 图 2-3 规范化数据库表 图 2-4 显示了新的规范化后的数据库的内容。 你可以看到,在主产品表中没有明确指定制造商和原产地,而是引用了相关表中的标 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 24 识。可以关联一个表中的数据到另一个表中的数据,该事实正是关系型数据库名称和力量 的来源。 图 2-4 经过规范化的表的数据 尽管规范化是重要的,但可能会过度规范化数据。例如,可以为价格创建一个单独的 表。在此情况下,所有价值 1 美元的产品将引用价格表中的包含值$1 的行。虽然这样做消 除了所有价值 1 美元的产品的重复数据,但编写维护产品表和价格表之间的关系的代码将 是十分痛苦的。这是过度规范化,是需要避免的。 规范化是重要的,但还要知道对数据进行布局来优化数据的显示也同样重要。在 iPhone 或 iPad 上优化用户体验经常是一个困难和乏味的过程。用户期望快速和平滑的用户体验。 如果数据被过度规范化,那么它可能是一个优化的数据存储策略,但如果在运行时访问用 于显示的数据的代价较大,那么应用程序的性能将受到损失。要记住你正在为 CPU 性能有 限的移动平台编写应用程序。你将为使用过于复杂的 SQL 访问数据而付出代价。在某些情 况下最好选择使用重复数据而不是使用关系。 2.2.2 创建数据库 存在几个不同的方法用于创建、修改和填充 SQLite 数据库。首先看一下命令行接口。 在使用图形界面的今天,使用命令行接口似乎不太理想,但命令行有它自己的优点。 一个突出的优点是可以使用命令行接口和脚本创建和填充数据库。例如,可以编写一个 Perl 脚本,从如 Oracle 或 MySQL 这样的企业数据库中读出数据,然后使用该数据的子集创建 一个 SQLite 数据库。尽管脚本超出了本书的范围,但下面还是会演示一下如何使用命令行 工具创建和填充数据库。 还可以使用命令行接口导入文件数据到表中,读取和执行包含 SQL 的文件,以各种格 式输出数据库中的数据,如: ● 逗号分隔的值 第 2 章 iOS数据库:SQLite 25 ● 左对齐的列 ● HTML 代码 ● 表的 SQL 插入语句 ● 每行一个值 ● 被.separator 字符串分隔的值 ● 制表符分隔的值 ● TCL 列表元素 要启动命令行工具,需要启动一个终端窗口。然后将目录改为你想要存储数据库文件 的目录。对于当前示例来说,可在主目录的根目录下创建数据库,然后将它复制到后面将 创建的 Xcode 项目中。 通过在命令提示符后输入 sqlite3 catalog.db,启动命令行工具并创建新的数据库。该命 令启动命令行工具并附加数据库 catalog.db。执 行 ATTACH DATABASE 命令或者附加现有 数据库到 SQLite 工具,或者在指定文件不存在时创建新的数据库。可以附加多个数据库到 一个命令行工具实例并以数据库名称.表名称的格式使用点符号在每个数据库中引用数据。 可以使用这个强大的特性将数据从一个数据库迁移到另一个数据库。 除了在命令行中执行 SQL,命令行接口工具还提供各种元命令(metacommands),用来 控制工具自身。通过在命令行中输入.help 可以显示这些元命令。通过在命令行输 入.databases 可以看到哪些数据库附加到当前的工具实例。输入.exit 或.quit 可以退出命令行 工具。 要创建主产品表,在 SQLite 命令提示符处输入 CREATE TABLE 语句,如下所示: CREATE TABLE "main"."Product" ("ID" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , "Name" TEXT, "ManufacturerID" INTEGER, "Details" TEXT, "Price" DOUBLE, "QuantityOnHand" INTEGER, "CountryOfOriginID" INTEGER, "Image" TEXT ); 关于 SQL 语言的完整讨论超出了本书的范围。如果你想学习更多的关于 SQL 语言的 知识,可以参考 Allen Taylor(Wiley,2010)撰著的 SQL For Dummies。 前面的 SQL 语句在主数据库中创建名为 Product 的表。它添加了在 ERD 中设计的字段。 最后,它将 ID 字段指定为 PRIMARY KEY(主键)并且将其指定为一个 AUTOINCREMENT(自 动增长)字段。这意味着你不需要提供 ID 值;数据库引擎将自动产生它们。 现在已经创建了 Product 表,下面继续创建 Manufacturer 和 Country 表。在命令提示符 后输入下列 SQL 命令: CREATE TABLE "main"."Manufacturer" ("ManufacturerID" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , "Name" TEXT NOT NULL ); CREATE TABLE "main"."Country" ("CountryID" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 26 "Country" TEXT NOT NULL ); 在编写本书时,Mac OS X Lion 操作系统提供的 SQLite 引擎的版本是 3.7.7。该版本支 持外键约束;然而,为了简单起见,创建的表没有启用外键约束。 外键约束允许数据库验证子表中用作外键的值被映射到了存储在父表中的值。在该模 型中,Product表中的ManufacturerID字段是一个外键,它引用Manufacturer表中的值。在 Manufacturer表中,ManufacturerID字段是它的主键。如果想要数据库实施规则——在Product 表中使用ManufacturerID对应的制造商必须存在于Manufacturer表中,则需要将Product表中 的ManufacturerID定义为一个外键,其引用Manufacturer表中的ManufacturerID。 刚才已经成功创建了数据库。你应该有了一个包含三个表——Product、Manufacturer 和 CountryOfOrigin 的数据库文件。现在可以将一些数据放到表中了。 2.2.3 填充数据库 虽然数据库很重要,但关键的是数据。可以在命令行中使用 INSERT SQL 语句一次填 充一项数据到数据库中。 1. 使用 INSERT 命令创建记录 图 2-5 显示了 INSERT 语句的语法。 图 2-5 INSERT 语句的语法 假如你不熟悉 SQL 语法图,那么下面将快速说明一下如何读懂它。 开始和结束处的空心圆是终止符。它们指示了 SQL 语句的起点和终点。从终止符出来 的箭头指示语句的主要分支。 关键字全部大写。主分支上的关键字是必需的。所以,若 INSERT 语句是有效的 SQL 语句,则其必须包含 INSERT、INTO 和 VALUES 关键字。不在主分支上的关键字都是可 选的。可选关键字的多个选择项是左对齐的。例如,INSERT 后面的 OR 是可选的。如果 使用 OR,则必须选择 ROLLBACK、ABORT、REPLACE、FAIL 或 IGNORE 中的一个并 且只能选择一个。 第 2 章 iOS数据库:SQLite 27 不全部大写的文本是用户提供的数据。因此,图 2-5 中的 INSERT SQL 指示用户需要 指定用来插入数据的数据库名和表名。另外,用户必须指定用来插入数据的列。最后是被 插入的值。 可以使用下列 INSERT 语句插入一行到 Product 表中: INSERT INTO "main"."Product" ("Name","ManufacturerID","Details","Price","QuantityOnHand", "CountryOfOriginID","Image") VALUES ('Widget A','1','Details of Widget A','1.29','5','1', 'Canvas_1'); 尽管这么做是可以的,但使用 SQL 一次插入一行数据是低效的。使用前面提到的命令 行工具可以把文本文件导入到数据库。当从另外一个数据库转储数据时这就会被派上用场, 另外的那个数据库可以是微软电子表格,或只是一个文本文件。可以为每个数据库表创建 一个文本文件并使用导入功能将数据导入到数据库,而不是输入每条 INSERT 语句。 在主目录中创建名为 products.txt 的文本文件并包含下列数据。注意使用制表符作为字 段间的分隔符。还可以从本书的配套网站上下载该文件。 1 Widget A 1 Details of Widget A 1.29 5 1 Canvas_1 2 Widget B 1 Details of Widget B 4.29 15 2 Canvas_2 3 Widget X 1 Details of Widget X 0.29 25 3 Canvas_3 4 Widget Y 1 Details of Widget Y 1.79 5 3 Canvas_4 5 Widget Z 1 Details of Widget Z 6.26 15 4 Canvas_5 6 Widget R 1 Details of Widget R 2.29 45 1 Canvas_6 7 Widget S 1 Details of Widget S 3.29 55 1 Canvas_7 8 Widget T 1 Details of Widget T 4.29 15 2 Canvas_8 9 Widget L 1 Details of Widget L 5.29 50 3 Canvas_9 10 Widget N 1 Details of Widget N 6.29 50 3 Canvas_10 11 Widget E 1 Details of Widget E 17.29 25 4 Canvas_11 12 Part Alpha 2 Details of Part Alpha 1.49 25 1 Canvas_12 13 Part Beta 2 Details of Part Beta 1.89 35 1 Canvas_13 14 Part Gamma 2 Details of Part Gamma 3.46 45 2 Canvas_14 15 Device N 3 Details of Device N 9.29 15 3 Canvas_15 16 Device O 3 Details of Device O 21.29 15 3 Canvas_16 17 Device P 3 Details of Device P 51.29 15 4 Canvas_17 18 Tool A 4 Details of Tool A 14.99 5 1 Canvas_18 19 Tool B 4 Details of Tool B 44.79 5 1 Canvas_19 20 Tool C 4 Details of Tool C 6.59 5 1 Canvas_20 21 Tool D 4 Details of Tool D 8.29 5 1 Canvas_21 在文本文件中使用制表符分隔的每一列表示数据库中的一个字段。字段的顺序必须和 你使用 CREATE TABLE 命令创建它们的顺序一致。因此,在这里字段的顺序是 ID、Name、 ManufacturerID、Details、Price、QuantityOnHand、CountryOfOriginID 和 Image。 将数据文件导入到数据库中时,如果还没有打开 SQLite 命令提示符,则先打开它。输 入命令.separator "\t",指定使用制表符\t 作为数据文件中字段的分隔符。接着输入.import "products.txt" Product,导入 products.txt 文件到 Product 表中。现在已经将数据成功导入到 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 28 数据库中了。 2. 使用 SELECT 命令读取行 要验证数据是否被成功导入,需要使用 SQL SELECT 语句显示它们。SELECT 语句的 语法如图 2-6 所示。 图 2-6 SELECT 语句的语法 输入 select * from Product;以查看产品表中的所有行。输出结果应该如下所示: 1 Widget A 1 Details of Widget A 1.29 5 1 Canvas_1 2 Widget B 1 Details of Widget B 4.29 15 2 Canvas_2 3 Widget X 1 Details of Widget X 0.29 25 3 Canvas_3 4 Widget Y 1 Details of Widget Y 1.79 5 3 Canvas_4 5 Widget Z 1 Details of Widget Z 6.26 15 4 Canvas_5 6 Widget R 1 Details of Widget R 2.29 45 1 Canvas_6 7 Widget S 1 Details of Widget S 3.29 55 1 Canvas_7 8 Widget T 1 Details of Widget T 4.29 15 2 Canvas_8 9 Widget L 1 Details of Widget L 5.29 50 3 Canvas_9 10 Widget N 1 Details of Widget N 6.29 50 3 Canvas_10 11 Widget E 1 Details of Widget E 17.29 25 4 Canvas_11 12 Part Alpha 2 Details of Part Alpha 1.49 25 1 Canvas_12 13 Part Beta 2 Details of Part Beta 1.89 35 1 Canvas_13 14 Part Gamma 2 Details of Part Gamma 3.46 45 2 Canvas_14 15 Device N 3 Details of Device N 9.29 15 3 Canvas_15 16 Device O 3 Details of Device O 21.29 15 3 Canvas_16 17 Device P 3 Details of Device P 51.29 15 4 Canvas_17 18 Tool A 4 Details of Tool A 14.99 5 1 Canvas_18 19 Tool B 4 Details of Tool B 44.79 5 1 Canvas_19 20 Tool C 4 Details of Tool C 6.59 5 1 Canvas_20 21 Tool D 4 Details of Tool D 8.29 5 1 Canvas_21 这和输入数据文件是相同的,所以你可以继续进行后面的工作了。 在主目录中创建另一个名为 manufacturers.txt 的文本文件并包含下列数据: 1 Spirit Industries 2 Industrial Designs 第 2 章 iOS数据库:SQLite 29 3 Design Intl. 4 Tool Masters 通过输入.import "manufacturers.txt" Manufacturer,导入制造商数据到 Manufacturer 表 中。可以通过输入 select * from manufacturer;,使用 SQL SELECT 语句再次验证数据已经 被正确导入。 最后,在主目录中创建另外一个文本文件,将其命名为 countries.txt 并包含下列数据: 1 USA 2 Taiwan 3 China 4 Singapore 通过输入.import "countries.txt" Country,导入 countries.txt 文件到 Country 表中以导入 产地数据到数据库中。可以通过输入 select * from country;,再次使用 SQL SELECT 语句验 证数据已经被正确导入。 现在数据库已经有了数据,可以随意使用你知道的标准 SQL 语句进行实验!例如,要 想看到使用价格排序的所有产品,可以输入 select name,price from product order by price;。 查询结果如下所示: Widget X 0.29 Widget A 1.29 Part Alpha 1.49 Widget Y 1.79 Part Beta 1.89 Widget R 2.29 Widget S 3.29 Part Gamma 3.46 Widget B 4.29 Widget T 4.29 Widget L 5.29 Widget Z 6.26 Widget N 6.29 Tool C 6.59 Tool D 8.29 Device N 9.29 Tool A 14.99 Widget E 17.29 Device O 21.29 Tool B 44.79 Device P 51.29 可以使用标准 SQL 语法连接表。例如,可以使用 SQL 语句 SELECT name, country FROM Product, country where product.countryoforiginid=country.countryid;显示每个产品及其 原产地名称。结果如下: Widget A USA 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 30 Widget B Taiwan Widget X China Widget Y China Widget Z Singapore Widget R USA Widget S USA Widget T Taiwan Widget L China Widget N China Widget E Singapore Part Alpha USA Part Beta USA Part Gamma Taiwan Device N China Device O China Device P Singapore Tool A USA Tool B USA Tool C USA Tool D USA 还可以使用 WHERE 子句过滤数据。要查找所有中国制造的产品,可以使用下列查询: SELECT name, country FROM Product, country where product.countryoforiginid=country.countryid and country.country="China";. 查询结果是所有中国制造的产品列表: Widget X China Widget Y China Widget L China Widget N China Device N China Device O China 2.2.4 可视化 SQLite 数据库的工具 与强大的 SQLite 命令行接口一样,有时使用图形用户界面(GUI)检查数据库比较方便。 有很多应用程序提供该功能,可以在 SQLite 网站 http://www.sqlite.org/cvstrac/wiki?p= ManagementTools 找到它们的列表。 可随意尝试运行 SQLite 网站列出的任何或所有应用程序。这些应用程序的价格从免费 到几百美元不等,它们提供各种各样的功能,包括从各种工具和商业数据库导入/导出、图 形 ER 建模、带有语法高亮显示的 SQL 编辑器以及许多其他高级功能。如果想要将 SQLite 用于企业应用程序,则其中的某个应用程序是值得花钱购买的。 如果开发的 iOS 应用程序比较简单,不需要大量的数据库开发,那么我更喜欢使用 Firefox Web 浏览器的 SQLite Manager 插件。这是一个免费插件,可在 Google 代码网站 (http://code.google.com/p/sqlite-manager/)下载,其提供下列功能: 第 2 章 iOS数据库:SQLite 31 ● 创建和删除表、索引、视图和触发器的对话框界面。 ● 通过添加和删除列来修改表的功能。 ● 创建或打开现有 SQLite 数据库的功能。 ● 执行任意的 SQL 语句或简单地查看表中所有数据的功能。 ● 数据库设置的可视界面,消除了编写 pragma 语句以查看和修改 SQLite 库设置的 需要。 ● 导入表/视图的功能,例如,可导出为 CSV、SQL 或 XML 文件。 ● 从 CSV、SQL 或 XML 文件导入表的功能。 ● 显示所有表、索引、视图和触发器的树状视图。 ● 从任何表/视图浏览数据的界面。 ● 当浏览数据时编辑和删除记录的功能。 该插件非常容易安装和使用。通过单击 Create Table 图标可以使用该插件创建新表。 然后会显示一个对话框,其中包含了创建新表所需的所有数据,如图 2-7 所示。其中已经 使用 ERD 中的字段填充了该对话框。 图 2-7 使用 SQLite Manager 创建表 单击界面左侧窗格中 Tables 旁边的小三角查看数据库中的所有表。选择一个表,如图 2-8 中的 Product,以显示该表的细节。你可以看到用来创建表的原始 SQL 语句,表中的字 段数量、记录数量以及表中所有列的详细信息。还可以从该视图中添加、修改和删除列。 可以选择右侧窗格顶部的 Browse & Search 选项卡,查看和编辑选择的表中的数据, 如图 2-9 中所示。 选择 Execute SQL 选项卡允许你在数据库上执行各种 SQL 语句。最后,DB Settings 选项卡允许你查看和编辑各种数据库设置,而这些设置原本只能在命令提示符中通过 pragma 语句查看和编辑。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 32 图 2-8 使用 SQLite Manager 查看表定义数据 图 2-9 使用 SQLite Manager 浏览表数据 2.3 连接到数据库 现在已经创建了目录数据库,可以编写用来查看目录的 iOS 应用程序了。首先需要创 建使用表视图显示目录的应用程序。单击表视图的单元格应该可以进入一个详情页面,该 页面用于显示选中的目录项的详细信息。要创建该界面,需要能够连接到数据库并在其上 运行 SQL 语句。还需要使用 Navigation Controller 实现主从界面。 第 2 章 iOS数据库:SQLite 33 如前面提到的,在开始编码前创建用户的应用界面原型是个好主意。这有助于让客户 认同你为满足他们的需求而设计的界面。来回移动原型的界面元素或重新设计原型的外观 远远要比修改实际应用的代码简单。尽可能早地发现设计问题可以避免昂贵和耗时的软件 修改。在向客户解释应用看起来应该是什么样子时图片是大有帮助的。图 2-10 显示了 OmniGraffle 中的一个界面原型。 该界面可能看起来不太漂亮,但它足以完成任务。在下一章将会对它进行一点修饰。 然而,当前它将演示如何从 SQLite 数据库中获取数据。 2.3.1 启动项目 对于本项目,将实现一个主从界面。如在图 2-10 中所看到的,主屏幕显示整个产品目 录,单击一项应该显示一个有关该项详情的屏幕。UINavigationController 对于创建这种界 面来说是非常完美的。要开始项目,首先打开 Xcode 并使用 Master-Detail Application 模板 创建新的项目。 图 2-10 应用程序界面原型 当创建新的项目时,在 New Project 对话框中为 iPhone 设置 Device Family 并在 Choose 选项中选中 Use Automatic Reference Counting 复选框。其余复选框可保持未选中状态,如 图 2-11 所示。 该模板创建一个包含两个 Interface Builder xib 文件的项目。MasterViewController.xib 文件包含保存主数据的表视图。DetailViewController.xib 文件是一个占位符,你将使用详情 数据填充它。 AppDelegate 包含一个 NavigationController,它管理应用程序的导航。AppDelegate 将 NavigationController 的根视图设置为 MasterViewController。在 AppDelegate 的 application: didFinishLaunchingWithOptions:方法中可看到此代码。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 34 图 2-11 项目选项对话框 1. UINavigationController 导航控制器用于显示和管理视图控制器的层次结构。当需要显示有层次的数据时,应 该考虑使用 UINavigationController。导航控制器使用“导航栈”管理显示状态。当准备好 显示视图控制器时,将其压入导航栈。按下 Back(后退)按钮会导致当前视图控制器被弹出 导航栈。在栈的底部是根视图控制器——在本例中是 MasterViewController。 目录应用程序使用 UINavigationController 实现导航功能。图 2-12 中的图表显示具有导 航栈的应用程序的界面原型。 左侧是显示在UITableView中的产品目录,该 UITableView包含在MasterViewController 中。在表视图中选择一行会导致 DetailViewController 被压入导航栈。其可在图的右侧看到。 导航栈的状态显示在图的底部。 图 2-12 应用程序屏幕和导航栈状态 第 2 章 iOS数据库:SQLite 35 单击详情屏幕顶部导航栏中的 Catalog 按钮将导致 DetailViewController 从导航栈中弹 出,这样会再次显示 MasterViewController。最重要的是要记住 UINavigationController 总是 显示位于导航栈顶部的视图控制器。 你可以看到是什么让导航控制器适合显示层次数据。当用户导航到下一层级时,应用 程序将视图控制器压入堆栈。当用户按下 Back 按钮时,视图控制器从堆栈中弹出,从而 导航回上一层级。 2. UITableViewController 如果查看 MasterViewController 的头文件代码,你会注意到 MasterViewController 不像 上一章那样是 UIViewController 的子类。相反,它是 UITableViewController 的子类。 当实现控制表视图的视图控制器时,可以子类化 UITableViewController 而不是 UIViewController。UITableViewController 是一个便捷类。当使用 UITableViewController 时, 不需要声明你将实现的 UITableViewDataSource 和 UITableViewDelegate 协议。 UITableViewController 还有一个和其相关的表视图。可通过 tableView 属性来获得该表 视图的引用。因为子类化的是 UITableViewController,所以不需要像上一章那样考虑创建 表视图的问题。只需要实现模型和控制器。然而,仍然需要负责实现上一章中实现过的 numberOfSectionsInTableView、numberOfRowsInSection 和 cellForRowAtIndexPath 方法。 #pragma mark – Table View 节强调了必须实现的表视图方法。你将注意到 Xcode 模板 在 MasterViewController(MasterViewController.m)实现文件的底部把这些方法聚合在一起。 因为 UITableView 从 MasterViewController 的 XIB 文件(MasterViewController.xib)加载, 所以其 dataSource 和 delegate 属性读取自 XIB。这些属性均被设置为 XIB 中的 File’s Owner, 即代码中的 self。这是没问题的,因为 MasterViewController 就是 UITableView 的 delegate 和 dataSource。 2.3.2 模型类 通过简单地基于 Master-Detail Application 模 板创建项目,你得到了大量免费的功能。实际上, 如果此时构建和运行该应用程序,你应能得到一 个如图 2-13 这样的界面。 目前还没有添加代码,但已经有了导航栏(顶 部蓝灰区域)和表视图(横线)。可以使用加号按钮 添加新行,使用 Edit 按钮编辑行集合。现在需要 使用数据填充该表视图。 为了和推荐的 iOS 的应用程序架构一致,需 要遵循模型-视图-控制器设计模式来设计该应用 程序。目前已经有了视图(在 XIB 文件中)和控制器 (Master View Controller 和 Detail View Controller); 图 2-13 运行 Master-Detail Application 模板 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 36 还需要一个模型。需要设计一个表示数据的模型类。该模型类还应该具有返回数据库中的 行数的方法和提供对特定行的数据进行访问的方法。 对于此应用程序,将基于 Product 类创建模型。Product 类将反映数据库中 Products 表 的字段。该模型是 Product 对象的集合。 要实现该模型,先创建一个新的 Objective-C 类,将其命名为 Product。在该类的头文 件中添加对应每个数据库字段的属性。下面是头文件代码: #import @interface Product : NSObject { int ID; NSString* name; NSString* manufacturer; NSString* details; float price; int quantity; NSString* countryOfOrigin; NSString* image; } @property (nonatomic) int ID; @property (strong, nonatomic) NSString *name; @property (strong, nonatomic) NSString *manufacturer; @property (strong, nonatomic) NSString *details; @property (nonatomic) float price; @property (nonatomic) int quantity; @property (strong, nonatomic) NSString *countryOfOrigin; @property (strong, nonatomic) NSString *image; @end 该代码简单地为每个数据库字段声明了一个成员变量并接着创建了用于访问每个字 段的属性。 该类的实现甚至更简单: #import "Product.h" @implementation Product @synthesize ID; @synthesize name; @synthesize manufacturer; @synthesize details; @synthesize price; @synthesize quantity; @synthesize countryOfOrigin; @synthesize image; @end 第 2 章 iOS数据库:SQLite 37 此处只是合成了在头文件中声明的所有属性。此时非常适合构建该应用程序并验证其 是否有错误。 2.3.3 DBAccess 类 现在已经完成了模型类,需要编写从数据库中获取数据并放入到模型中的代码。通常, 抽象对数据库的访问是个好主意。这意味着编写一个通用类来执行常用数据库功能。如果 想要以后使用不同的数据库引擎,这样做会提供灵活性。为此,需要创建一个数据库访问 类来和数据库交互。该类提供方法用于初始化数据库、关闭数据库,以及更重要的——创 建和返回一个 Product 对象的数组。 在编写数据库访问类的代码前,需要将 SQLite 数据库添加到 Xcode 项目中。右键单 击 Supporting Files 文件夹并选择 Add Files to “Catalog”,导航到主目录或其他存储 catalog 数据库的目录,选择该数据库文件,按下对话框中的 Add 按钮,会将 SQLite 数据库添加 到项目的 Supporting Files 文件夹中。确保选中了 Copy items into destination group’s folder (if needed)复选框,如图 2-14 所示。 图 2-14 将现有文件添加到项目中 要创建数据库访问类,先要创建一个新的名为 DBAccess 的 Objective-C 类。在该类的 头文件 DBAccess.h 中添加 sqlite3.h 的导入语句,因为此数据访问类需要使用 sqlite3 库的 函数。 另外还要在头文件中添加计划实现的三个方法签名:getAllProducts、closeDatabase 和 initializeDatabase。closeDatabase 和 initializeDatabase 是自说明的。getAllProducts 方法返回 目录中所有 Product 对象的一个数组。因为在此类中引用了 Product 对象,所以需要添加 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 38 Product.h 的导入语句。 DBAccess.h 头文件应如下所示: #import // This includes the header for the SQLite library. #import #import "Product.h" @interface DBAccess : NSObject { } - (NSMutableArray*) getAllProducts; - (void) closeDatabase; - (void)initializeDatabase; @end 在 DBAccess 类的实现中,添加类变量来保存对数据库的引用: // Reference to the SQLite database. sqlite3* database; 该类变量在 initializeDatabase 函数中设置。然后该类的其他函数可用来访问数据库。 现在创建调用者用来初始化类实例的 init 函数。在 init 中将使用一个内部调用来初始 化数据库。这个 init 函数应如下所示: -(id) init { // Call super init to invoke superclass initiation code if ((self = [super init])) { // Set the reference to the database [self initializeDatabase]; } return self; } initializeDatabase 函数用于初始化数据库。它先获得数据库路径,然后尝试打开它。下 面是 initializeDatabase 的代码: // Open the database connection - (void)initializeDatabase { // Get the database from the application bundle. NSString *path = [[NSBundle mainBundle] pathForResource:@"catalog" ofType:@"db"]; 第 2 章 iOS数据库:SQLite 39 // Open the database. if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) { NSLog(@"Opening Database"); } else { // Call close to properly clean up sqlite3_close(database); NSAssert1(0, @"Failed to open database: '%s'.", sqlite3_errmsg(database)); } } 从代码中可看到需要数据库文件的路径。因为 catalog.db 文件被放到了 Supporting Files 文件夹中,所以该数据库将被部署到主应用包中的设备上。这里有一个便捷类 NSBundle,可用于获得应用包的信息。该类的 mainBundle 方法返回主应用包的引用,其 pathForResource:ofType:方法返回包中指定文件的路径。因为这里指定资源为 catalog,类 型 为 db,所以该方法返回 catalog.db 的路径。 下一步,使用 C 函数 sqlite3_open 打开数据库连接。传递给该函数的参数是数据库的 路径和 sqlite3*类型的变量的地址。第二个参数将用数据库句柄设置。sqlite3_open 函数返 回 int。如果一切正常,它就返回 SQLITE_OK 常量。如果出错,它就返回一个错误码。最 常见的错误是 SQLITE_ERROR,代表数据库没有找到;以及 SQLITE_CANTOPEN,代表 因为某些原因数据库文件无法打开。在 sqlite3.h 包含文件中可找到完整的错误码列表。 确保返回值是 SQLITE_OK 并记录正在打开数据库。如果返回的是错误码,则关闭数 据库并记录错误消息。 接下来,添加干净地关闭数据库连接的方法。这非常简单,即调用 sqlite3_close 并传 入一个数据库句柄,如下所示: -(void) closeDatabase { // Close the database. if (sqlite3_close(database) != SQLITE_OK) { NSAssert1(0, @"Error: failed to close database: '%s'.", sqlite3_errmsg(database)); } } sqlite3_close 函数的返回值和 sqlite3_open 类似。如果调用 sqlite3_close 失败,可使用 sqlite3_errmsg 函数获得错误码的文本消息并将其输出到控制台。 此时应该尝试构建应用。构建动作会因为没有找到 SQLite 函数而失败。尽管包含合适 的头文件,但编译器不知道从哪里查找 SQLite 库的二进制文件,所以需要将 libsqlite 框架 添加到 Xcode 项目中。单击 Project Navigator 顶部的 Catalog 项目图标。在 Editor 窗格左侧 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 40 选择 Catalog Target。在 Editor 窗格顶部,选择 Build Phases 选项卡。在 Editor 窗格中展开 Link Binary with Libraries 项,然后单击已经包含在项目中的框架列表下面的加号。从框架 列表中选择 libsqlite3.0.dylib。现在应该可以成功构建项目了。此时仍然会收到一个警告, 告知你 getAllProducts 方法没有实现,可以通过实现该方法来修正此问题。 下面是数据库访问类的核心:getAllProducts 方法。该方法将被实现用来返回 Product 对象数组,这些对象代表产品目录数据库中的记录。此方法分配一个 NSMutableArray 用来 保存产品列表,构造 SQL 语句,执行语句,以及遍历结果,为查询中返回的每一行构造一 个 Product 对象。 下面先从声明和初始化保存产品的数组来开始实现该方法。这里使用 NSMutableArray,因为在从数据库中获取行时你想要逐个将 Product 对象添加到数组中。 普通的 NSArray 类是不可修改的,所以不能即时向其中添加项。 下面是 getAllProducts 方法的开始部分: - (NSMutableArray*) getAllProducts { // The array of products that we will create NSMutableArray *products = [[NSMutableArray alloc] init]; getAllProducts 方法下一步要实现的是声明一个 char*变量并使用 SQL 语句设置它: // The SQL statement that we plan on executing against the database const char *sql = "SELECT product.ID,product.Name, \ Manufacturer.name,product.details,product.price,\ product.quantityonhand, country.country, \ product.image FROM Product,Manufacturer, \ Country where manufacturer.manufacturerid=product.manufacturerid \ and product.countryoforiginid=country.countryid"; 下面是该 SQL 语句的一个更易读的形式: SELECT product.ID, product.Name, Manufacturer.name, product.details, product.price, product.quantityonhand, country.country, product.image FROM Product,Manufacturer, Country WHERE manufacturer.manufacturerid=product.manufacturerid AND product.countryoforiginid=country.countryid 本书假设你了解 SQL,所以不深入讨论编写 SQL 语句的所有细节。然而,这里简单 说明一下从三个表中获取数据的 SQL 语句,这三个表在该语句的 FROM 子句中指定,分 别是:Product、Manufacturer 和 Country。 第 2 章 iOS数据库:SQLite 41 在该查询的 SELECT 部分可看到从这些表中选择了哪个字段。指定选择的字段的格式 是 table.field。因此,该查询从 Product 表中选择了 ID 字段,然后从 Product 表中选择了 Name 字段。 最后,在 WHERE 子句中设置了联接。你只想要来自制造商表的 manufacturerid 与产 品表的 manufacturerid 相同的数据。同样,你只想要来自产地表的 countryid 与产品表中的 countryoforiginid 相同的数据。这使你可以在查询和应用程序中显示实际的制造商名称和产 地名称,而不是仅仅显示无意义的 ID 编号。 我建议在如 SQLite Manager 或命令行中编写和测试查询。如果可以运行查询并立即看 到结果,就可以很容易地开发查询,尤其是当开发更复杂的查询时更是如此。图 2-15 显示 了运行在 SQLite Manager 中的查询。当执行查询时可得到期望的结果。 图 2-15 使用 SQLite Manager 测试查询 要在代码中运行 SQL 查询,需要创建一个 SQLite 语句对象——该对象将在数据库上 执行 SQL 语句。然后准备该 SQL 语句。 // The SQLite statement object that will hold the result set sqlite3_stmt *statement; // Prepare the statement to compile the SQL query into byte-code int sqlResult = sqlite3_prepare_v2(database, sql, -1, &statement, NULL); sqlite3_prepare_v2 函数的参数为一个数据库连接、一条 SQL 语句、该 SQL 语句的最 大长度或-1(表明读取到第一个 null 终止符)、用来遍历结果的语句句柄,以及指向该 SQL 语句第一个字节的指针或此处使用的 NULL。 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 42 和本章中运行的其他 SQLite 命令一样,sqlite3_prepare_v2 返回一个 int,它可以是 SQLITE_OK 或一个错误码。 应该注意准备 SQL 语句并不实际执行该语句。直到调用 sqlite3_step 函数开始获取行 之前该语句才会执行。 如果该函数的返回值为 SQLITE_OK,则可以使用 sqlite3_step 函数一次一行地遍历结果: if ( sqlResult== SQLITE_OK) { // Step through the results - once for each row. while (sqlite3_step(statement) == SQLITE_ROW) { 对于每一行,分配一个 Product 对象: // Allocate a Product object to add to products array Product *product = [[Product alloc] init]; 现在已经从行中获取了数据。可以使用一组称作“结果集”接口的函数获取指定字段。 使用其中的哪个函数取决于想要获取的列的数据类型。下面是可用的函数列表: const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); int sqlite3_column_bytes(sqlite3_stmt*, int iCol); int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); double sqlite3_column_double(sqlite3_stmt*, int iCol); int sqlite3_column_int(sqlite3_stmt*, int iCol); sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); int sqlite3_column_type(sqlite3_stmt*, int iCol); sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); 该列表中的每个函数的第一个参数是预编译的语句,第二个参数是想要获取的字段在 SQL 语句中的索引。该索引是基于 0 的,所以要获得 SQL 语句中的第一个字段,即 int 类 型的产品 ID,需要使用下面这个函数: sqlite3_column_int(statement, 0); 若要从数据库中获取文本或字符串,可使用 sqlite3_column_text 函数。我喜欢创建一 个 char*变量来保存从数据库中获取的字符串。接着使用三元操作符(?:)对该变量进行判断 处理,如果其非空,则使用该字符串;如果其为空,则使用一个空字符串。下面是从数据 库中获取所有数据并填充产品对象的代码: // The second parameter is the column index (0 based) in // the result set. char *name = (char *)sqlite3_column_text(statement, 1); char *manufacturer = (char *)sqlite3_column_text(statement, 2); char *details = (char *)sqlite3_column_text(statement, 3); char *countryOfOrigin = (char *)sqlite3_column_text(statement, 6); char *image = (char *)sqlite3_column_text(statement, 7); // Set all the attributes of the product product.ID = sqlite3_column_int(statement, 0); 第 2 章 iOS数据库:SQLite 43 product.name = (name) ? [NSString stringWithUTF8String:name] : @""; product.manufacturer = (manufacturer) ? [NSString stringWithUTF8String:manufacturer] : @""; product.details = (details) ? [NSString stringWithUTF8String:details] : @""; product.price = sqlite3_column_double(statement, 4); product.quantity = sqlite3_column_int(statement, 5); product.countryOfOrigin = (countryOfOrigin) ? [NSString stringWithUTF8String:countryOfOrigin] : @""; product.image = (image) ? [NSString stringWithUTF8String:image] : @""; 最后将产品添加到 products 数组并移到下一行: // Add the product to the products array [products addObject:product]; } 在遍历完结果集后,调用 sqlite3_finalize 以释放和预编译的语句相关的资源。接着记 录错误并返回 products 数组: // Finalize the statement to release its resources sqlite3_finalize(statement); } else { NSLog(@"Problem with the database:"); NSLog(@"%d",sqlResult); } return products; } 整个数据库访问类应如代码清单 2-1 所示。 代码清单 2-1:DBAccess.m #import "DBAccess.h" @implementation DBAccess // Reference to the SQLite database. sqlite3* database; -(id) init { // Call super init to invoke superclass initiation code if ((self = [super init])) { // Set the reference to the database [self initializeDatabase]; } return self; } 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 44 // Open the database connection - (void)initializeDatabase { // Get the database from the application bundle. NSString *path = [[NSBundle mainBundle] pathForResource:@"catalog" ofType:@"db"]; // Open the database. if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) { NSLog(@"Opening Database"); } else { // Call close to properly clean up sqlite3_close(database); NSAssert1(0, @"Failed to open database: '%s'.", sqlite3_errmsg(database)); } } -(void) closeDatabase { // Close the database. if (sqlite3_close(database) != SQLITE_OK) { NSAssert1(0, @"Error: failed to close database: '%s'.", sqlite3_errmsg(database)); } } - (NSMutableArray*) getAllProducts { // The array of products that we will create NSMutableArray *products = [[NSMutableArray alloc] init]; // The SQL statement that we plan on executing against the database const char *sql = "SELECT product.ID,product.Name, \ Manufacturer.name,product.details,product.price,\ product.quantityonhand, country.country, \ product.image FROM Product,Manufacturer, \ Country where manufacturer.manufacturerid=product.manufacturerid \ and product.countryoforiginid=country.countryid"; // The SQLite statement object that will hold our result set sqlite3_stmt *statement; // Prepare the statement to compile the SQL query into byte-code int sqlResult = sqlite3_prepare_v2(database, sql, -1, &statement, NULL); 第 2 章 iOS数据库:SQLite 45 if ( sqlResult== SQLITE_OK) { // Step through the results - once for each row. while (sqlite3_step(statement) == SQLITE_ROW) { // allocate a Product object to add to products array Product *product = [[Product alloc] init]; // The second parameter is the column index (0 based) in // the result set. char *name = (char *)sqlite3_column_text(statement, 1); char *manufacturer = (char *)sqlite3_column_text(statement, 2); char *details = (char *)sqlite3_column_text(statement, 3); char *countryOfOrigin = (char *)sqlite3_column_text(statement, 6); char *image = (char *)sqlite3_column_text(statement, 7); // Set all the attributes of the product product.ID = sqlite3_column_int(statement, 0); product.name = (name) ? [NSString stringWithUTF8String:name] : @""; product.manufacturer = (manufacturer) ? [NSString stringWithUTF8String:manufacturer] : @""; product.details = (details) ? [NSString stringWithUTF8String:details] : @""; product.price = sqlite3_column_double(statement, 4); product.quantity = sqlite3_column_int(statement, 5); product.countryOfOrigin = (countryOfOrigin) ? [NSString stringWithUTF8String:countryOfOrigin] : @""; product.image = (image) ? [NSString stringWithUTF8String:image] : @""; // Add the product to the products array [products addObject:product]; } // Finalize the statement to release its resources sqlite3_finalize(statement); } else { NSLog(@"Problem with the database:"); NSLog(@"%d",sqlResult); } return products; } @end 2.3.4 参数化查询 尽管在本例中没有使用参数化查询,但它是可能的并且相当常见。 例如,如果想要创建一个只返回美国制造的产品的查询,可以使用下列 SQL: SELECT Product.name, country.country FROM country,product WHERE countryoforiginid=countryid and country='USA' 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 46 如果只是想要美国制造的产品的列表,那么该查询相当完美。但如果想要在运行时决 定显示哪个产地的产品,则需要使用参数化查询。 参数化该查询的第一步是使用问号(?)替换想要参数化的数据。所以 SQL 语句将变成 这样: SELECT Product.name, country.country FROM country,product WHERE countryoforiginid=countryid and country=? 在使用 sqlite3_prepare_v2 预编译语句后,但在使用 sqlite3_step 遍历查询结果前,需要 绑定参数。记住预编译语句并不实际执行该语句。直到使用 sqlite3_step 遍历结果集时该语 句才会执行。 SQLite 提供一系列函数用于绑定参数,其使用方式和获取数据字段的方式类似。下面 是绑定函数: int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); int sqlite3_bind_double(sqlite3_stmt*, int, double); int sqlite3_bind_int(sqlite3_stmt*, int, int); int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); int sqlite3_bind_null(sqlite3_stmt*, int); int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); 使用的绑定函数应该对应要绑定的数据类型。每个绑定函数的第一个参数是预编译语 句。第二个参数是 SQL 语句中的参数索引(从 1 开始)。剩余的参数根据想要绑定的类型而 有所不同。完整的绑定函数的文档可参见 SQLite 网站。 要在运行时绑定文本,可使用 sqlite3_bind_text 函数,如下所示: sqlite3_bind_text (statement,1,value,-1, SQLITE_TRANSIENT); 在该绑定语句中,value 是在运行时确定的文本。在本例中,该文本是“USA”,但可 以将其设置为在运行时动态要求的任何值。这就是使用参数化查询的优点。当然,你可以 每次动态产生 SQL 语句,但参数化查询语句只需准备和编译一次,然后就可缓存和重用该 语句,所以其提供了更好的性能。 2.3.5 写入数据库 如果修改示例应用程序或创建自己的 SQLite 应用程序以尝试写入数据到数据库,你将 会遇到问题。在示例代码中使用的数据库位于应用包中,但应用包是只读的,所以尝试写 入到该数据库将导致错误。 第 2 章 iOS数据库:SQLite 47 为了能够写入数据库,需要创建一个可编辑的副本。在设备上将该可编辑的副本放入 文档目录中。设备上的每个应用程序都是“沙箱化”的并且只能访问它自己的文档目录。 下面的代码片段显示了如何检查可写的数据库是否已经存在,并且如果不存在,则创 建一个可编辑的副本。 // Create a writable copy of the default database from the bundle // in the application Documents directory. - (void) createEditableDatabase { // Check to see if editable database already exists BOOL success; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDir = [paths objectAtIndex:0]; NSString *writableDB = [documentsDir stringByAppendingPathComponent:@"catalog.db"]; success = [fileManager fileExistsAtPath:writableDB]; // The editable database already exists if (success) return; // The editable database does not exist // Copy the default DB to the application Documents directory. NSString *defaultPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"catalog.db"]; success = [fileManager copyItemAtPath:defaultPath toPath:writableDB error:&error]; if (!success) { NSAssert1(0, @"Failed to create writable database file:'%@'.", [error localizedDescription]); } } 下一步需要改变数据库访问代码来调用此函数,然后使用数据库的可编辑副本而不是 包中的副本。 2.3.6 显示目录 现在已经完成了 DBAccess 类,可以开始显示目录的工作了。在 MasterViewController 中实现从 DBAccess 类中获取产品数组并将其显示到表视图中的代码。 在头文件 MasterViewController.h 中,添加 Product 和 DBAccess 类的 import 语句: #import "Product.h" #import "DBAccess.h" 添加保存产品数组的属性: @property (strong, nonatomic) NSMutableArray* products; 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 48 在 iOS 6 中,可以可选地通过添加@synthesize 指令到实现文件中来合成属性。合成属 性让编译器为该属性产生设置和获取方法。在 iOS 6 中,编译器会自动合成属性。然而可 以另外自行定义获取方法、设置方法或同时定义两者。没有定义的方法会被编译器补上。 在 MasterViewController.m 中的实现类之后,添加从 DBAccess 类中获取产品数组的 viewDidLoad 方法: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // Get the DBAccess object; DBAccess *dbAccess = [[DBAccess alloc] init]; // Get the products array from the database self.products = [dbAccess getAllProducts]; // Close the database because we are finished with it [dbAccess closeDatabase]; } 另外,为了保持该例简单,移除了在屏幕顶部提供 Edit 和 Add 按钮的默认代码。 还应该修改 tableView:canEditRowAtIndexPah:方法来禁止编辑行数据: -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath: (NSIndexPath *)indexPath { // Return NO if you do not want the specified item to be editable. return NO; } 下一步需要实现表视图方法。第一件事是像上一章那样通过实现 numberOfRowsInSection 方法来告知表视图有多少行。该方法可以从产品数组中获得要显示到表视图中的项数: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.products count]; } 最后需要实现 cellForRowAtIndexPath 提供给表视图的与请求的行对应的表视图单元格。 该代码类似上一章的示例。不同之处在于现在是从 Product 对象中获得单元格的文本。该 代码以表视图请求的行作为索引从数组中查找 Product 对象。下面是 cellForRowAtIndexPath 方法的代码: // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 第 2 章 iOS数据库:SQLite 49 static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // Configure the cell. // Get the Product object Product* product = [self.products objectAtIndex:[indexPath row]]; cell.textLabel.text = product.name; return cell; } 要在导航控制器的顶部设置 Catalog 文本,跳转到 MasterViewController.m 实现文件中 的 initWithNibName:bundle:方法,将下面的行: self.title=NSLocalizedString(@"Master",@"Master"); 修改为: self.title=NSLocalizedString(@"Catalog", @"Catalog"); 现在应该可以构建和运行应用程序了。当运行应用 程序时,应能看到如图 2-16 中所示的产品目录。当单击 目录项时,什么也不会发生,但该应用程序应该显示单 击的产品的详细信息。要完成此功能,需要实现表视图 方法 didSelectRowAtIndexPath。然而,在实现这些功能 前,需要创建用户单击表中的产品后用来显示详细信息 的视图。 2.3.7 查看产品详情 当用户单击表中的某个产品时,应用程序应该导航 到产品详情屏幕。因为该屏幕和 NavigationController 一 起使用,所以它需要使用 UIViewController 来实现。主 从应用程序模板的一个好特性是模板自动创建从视图控制器。该控制器恰如其分地被称为 DetailViewController。 在 DetailViewController.h 头文件中,为想要显示的数据添加 Interface Builder 出口 (outlet)。你需要为每个标签控件添加一个出口属性,如下所示: 图 2-16 运行目录应用程序 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 50 @property (strong, nonatomic) IBOutlet UILabel* nameLabel; @property (strong, nonatomic) IBOutlet UILabel* manufacturerLabel; @property (strong, nonatomic) IBOutlet UILabel* detailsLabel; @property (strong, nonatomic) IBOutlet UILabel* priceLabel; @property (strong, nonatomic) IBOutlet UILabel* quantityLabel; @property (strong, nonatomic) IBOutlet UILabel* countryLabel; 对于由项目模板创建的但不会用到的默认 Interface Builder 出口属性,可以删除它: @property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel; 下面将在代码中将出口连接到用户界面(UI)小部件。在 Xcode 中创建用户界面的基础 知识超出了本书的范围。在创建 iOS 用户界面方面有几本好书,包括 Rob Napier 和 Mugunth Kumar 合著的 iOS 6 Programming Pushing the Limits (Wiley, 2012)。 通过将 Product 对象从 MasterViewController 传递给 DetailViewController 来实现传输用 户选择的产品数据到详情视图。因为将在代码中引用 Product 对象,所以需要将 Product 类 的导入语句添加到 DetailViewController 头文件中。完整的头文件应如下所示: #import #import "Product.h" @interface DetailViewController : UIViewController @property (strong, nonatomic) IBOutlet UILabel* nameLabel; @property (strong, nonatomic) IBOutlet UILabel* manufacturerLabel; @property (strong, nonatomic) IBOutlet UILabel* detailsLabel; @property (strong, nonatomic) IBOutlet UILabel* priceLabel; @property (strong, nonatomic) IBOutlet UILabel* quantityLabel; @property (strong, nonatomic) IBOutlet UILabel* countryLabel; @property (strong, nonatomic) id detailItem; @end 现在选择 DetailViewController.xib 文件。在此文件中,像在原型中设计的那样将一系 列 UILabel 添加到界面上。该界面看起来应该如图 2-17 所示。 下一步需要连接标签到 DetailViewController.h 头文件中创建的出口。确保在连接出口 前保存该头文件,否则在该头文件中创建的出口是无效的。 要连接出口,确保 DetailViewController.xib 文件打开。在 Interface Builder 窗口的左侧 应看到两组对象:Placeholders 和 Objects。在 Placeholders 组中,选择 File’s Owner。确保 Utilities 视图打开,并单击 Utilities 视图顶部的最后一个图标以显示 Connections Inspector。 在该窗格中,可看到所有的类出口和它们的连接。单击并拖动出口名字右侧的空心圆到用 户界面中该出口应该连接的控件。最终界面应该如图 2-18 所示。 第 2 章 iOS数据库:SQLite 51 图 2-17 产品详情视图 图 2-18 创建详情视图 在 DetailViewController.m 中,实现 configureView 方法以设置标签中的文本: - (void)configureView { // Update the user interface for the detail item. if (self.detailItem) { Product* theProduct = (Product*) self.detailItem; // Set the text of the labels to the values passed in the Product object 第Ⅰ部分 操作和显示 iPhone 和 iPad 上的数据 52 [self.nameLabel setText:theProduct.name]; [self.manufacturerLabel setText:theProduct.manufacturer]; [self.detailsLabel setText:theProduct.details]; [self.priceLabel setText:[NSString stringWithFormat: @"%.2f",theProduct.price]]; [self.quantityLabel setText:[NSString stringWithFormat:@"%d", theProduct.quantity]]; [self.countryLabel setText:theProduct.countryOfOrigin]; } } 主从应用程序模板创建了 configureView 方法用于配置视图。在 if(self.detailItem)块中 添加设置标签文本的代码,从而确保给出的对象是想要显示的对象。在此应用程序中, detailItem 将为 Product 对象,所以将 self.detailItem 转换为(Product *)。通常,因为 detailItem 是 id 类型的,所以可以在应用程序中使用 detailItem 显示来自任何类型的对象的数据。 模板代码在 detailItem 属性的设置方法中调用 configureView 方法,如下所示: - (void)setDetailItem:(id)newDetailItem { if (_detailItem != newDetailItem) { _detailItem = newDetailItem; // Update the view. [self configureView]; } } 因此,当从主视图控制器设置 detailItem 属性时,从视图控制器将使用该属性中的对 象来配置视图。 为了能导航到新的屏幕,需要修改MasterViewController 中的tableView: didSelectRowAtIndexPath:方法以在用户选择一个产品时显示详情屏幕。 在 MasterViewController 实现中,修改 tableView:didSelectRowAtIndexPath:方法中的代 码以将 detailViewController.detailItem 设置为用户选择的产品数组中的对象: - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (!self.detailViewController) { self.detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil]; } self.detailViewController.detailItem = [self.products objectAtIndex: [indexPath row]]; [self.navigationController pushViewController: self.detailViewController animated:YES]; } 第 2 章 iOS数据库:SQLite 53 以上就是所有内容了。现在可以构建和运行应用程序了。试着单击目录中的项。应用 程序应该进入到该项的详情页面。单击导航栏中的 Catalog 按钮应能回到 catalog 页面。单 击表视图中的另外一行应能显示该项的数据。 现在已经有了一个功能完整的目录应用程序!我知道它看起来并不完美,但在下一章 深入学习定制 UITableView 时将解决这个问题。 2.4 前往下一章 现在你已经有了一个功能齐全的应用程序,你可以按照自己的想法随意修改它!如修 饰界面,或将额外的字段添加到数据库表和 Product 类。 介绍 SQL 语言的好书有很多,所以如果你对本章使用的 SQL 语句感到困惑,可以参 看 Allen Taylor 撰写的 SQL For Dummies。 另一个你需要关注的资源是 SQLite 网站 http://www.sqlite.org/。在那里可以找到数据库 方面的以及用来访问数据库的 C 语言 API 方面的大量文档。 尽管你已经学习了如何从 SQLite 中获取数据并将其放入 iOS 应用程序,但目录应用程 序看起来不是很完美。下一章将学习如何通过定制表视图以更时髦的方式来显示数据。
还剩66页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

Alan_Jiang

贡献于2016-05-11

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!