iOS 7 iPhone iPad 应用开发技术详解


iOS 7: iPhone/iPad 应用开发技术详解 刘一道 著 图书在版编目(CIP)数据 iOS 7: iPhone/iPad 应用开发技术详解 / 刘一道著 . —北京:机械工业出版社,2013.11 ISBN 978-7-111-44051-2 I. i… II. 刘… III. 移动终端-应用程序-程序设计 IV. TN929.53 中国版本图书馆 CIP 数据核字(2013)第 218499 号 版权所有·侵权必究 封底无防伪标均为盗版 本书法律顾问 北京市展达律师事务所 本书由资深 Mac/iOS 开发工程师基于 iOS 7 撰写。内容全面,从 Objective-C 语法知识、iOS 功能特 性,到高级开发方法和技巧,几乎涵盖了中初级 iOS 开发工程师需要掌握的所有技术和知识;实战性强, 每个知识点都有辅助理解的小案例,最后还提供了两个综合性应用开发案例。内容循序渐进,是系统学 习 iOS 应用开发的经典著作。 全书共 20 章,分 4 部分。准备篇(第 1 ~ 2 章)介绍了 iOS、Objective-C 和 Xcode 的入门知识, 详细描述了 iOS 7 新特性、Objective-C 应用开发环境的搭建,以及 iPhone 应用开发的完整过程 ;语法篇 (第 3 ~ 8 章)详细讲解 Objective-C 的语法知识,其中包括类、对象、消息和协议,以及内存管理,重 点讲解了 Foundation 框架常用类的使用方法 ;基础篇(第 9 ~ 18 章)讲解了 iOS 应用开发的框架,包 含多状态和多任务、视图和视图控制器、事件和通知、音频和视频等,这些内容是本书的重点 ;实战篇 (第 19 ~ 20 章)详细讲解了两个实战案例的完整开发过程,分别是基于 iPad 的应用程序“精灵小书柜” 和基于 iPhone 的“弹球游戏”,目标是通过两个案例把前面学到知识应用到实践中,帮助读者真正掌握 iOS 应用开发的过程。 机械工业出版社(北京市西城区百万庄大街 22 号  邮政编码 100037) 责任编辑:白 宇            印刷 2013 年 11 月第 1 版第 1 次印刷 186mm×240mm · 33 印张 标准书号:ISBN 978-7-111-44051-2 定  价:79.00 元 凡购本书,如有缺页、倒页、脱页,由本社发行部调换 客服热线:(010)88378991 88361066       投稿热线:(010)88379604 购书热线:(010)68326294 88379649 68995259  读者信箱:hzjsj@hzbook.com 前  言 如何成为一名软件开发工程师 从大学毕业到如今,不知不觉敲代码已有十余载。从初期的“乱石穿空,惊涛拍岸,卷 起千堆雪”,到如今的“道可道也,非恒道也。名可名也,非恒名也”,真可谓“谈笑间,樯 橹灰飞烟灭”。少了份“浮躁”,多了份“平静与思考”。懂得了敲代码正如雕刻家雕刻作品 一样,需要一点一点精雕细刻,才可能创造出“艺术品”;懂得了“道”存在于代码之间, 正所谓“为学者日益,为道者日损,损之又损,以至于无为”。 对于软件开发这个殿堂,估计很多人会长久徘徊于门外,特别是那些想迈进这个大门的 初学者或者爱好者。实际上,当初的我也是带着很多的困惑和很大的压力步入这个行业的。 大学期间留给我们的多是“一无所有”的四年。而吾等众生,面对毕业之后父母“断 奶”之迫,需要自己靠双手来“自力更生”;而面对校园外的“招聘”,自己总是那么惶惶不 安缺乏“自信”,特别是参加过几场人才招聘会之后,真可谓“欲哭无泪”呀! 引用普希金的诗《假如生活欺骗了你》:“假如生活欺骗了你,不要伤心,不要哭泣 ;这 样的日子里,需要勇气 ;要相信快乐的日子总会来临。”只要你有梦想有激情,有强烈的学习 欲望,有较强的逻辑思维能力和一定的英语基础,热爱软件开发工作,同时能做到持之以恒, 相信你不但能成为一名职业的软件开发工程师,而且还能成为一名优秀的软件开发工程师。 这一行是靠能力说话的,同时,这又是一个充满激情的行业。不管你是否毕业,不管你 是否是计算机专业,甚至不管你是否上过大学,只要肯努力,有上进心,都可以加入软件开 发团队中。也许你就是中国未来的比尔·盖茨或者乔布斯,世界会因“你”而精彩(李开复 很喜欢这个座右铭)! IV 这里我还想说两点,希望对即将毕业或者刚步入大学的你有用: ‰‰ 对于高校在读的学生,不管将来要从事什么行业,建议早有规划,早做一些准备。以 免毕业做“啃老族”。 ‰‰ 对于即将大学毕业或者已经毕业的学生,建议最好不要参加社会上的“软件开发培 训”。一是需要一笔可观的培训费(如果经济条件好,参加也不错);二是不利于自学能 力的培养。因为从事软件开发行业,需要保持一颗不断学习的心,否则很容易被淘汰。 为什么学习 iPhone/iPad 应用开发技术 1)基于 iPhone/iPad 的应用开发已成为当今移动智能设备两大主流应用开发之一。 最近几年国内外从事基于 iPhone/iPad 开发的人员增速一直位于前列,已经形成了一个 庞大的开发队伍。无论是传统互联网还是移动互联网,iPhone/iPad 应用设计理念一直引导着 众多人模仿、推崇和跟随。iPhone/iPad 已成为当今移动智能设备应用发展的里程碑。 2)支持 iPhone/iPad 的操作系统 iOS 是当今移动智能设备运行最稳定的操作系统。 当今市场上应用最广的两大主流智能移动设备操作系统分别是 Android 和 iOS。前者系 统的稳定性、开销性和安全性成为“三座大山”,难以“移动”。很多用过 Android 手机的用 户都遇到过手机“死机”、“莫名重启”等问题,而 iOS 就很少发生这类情况。 3)iPhone/iPad 框架的原生态语言是 Objective-C。 选择 Objective-C 作为 iPhone/iPad 框架的原生态语言有许多方面的原因。 ‰‰ 首先,最主要的原因是,Objective-C 是面向对象的语言。iPhone/iPad 框架中的很多 功能只能通过面向对象技术来呈现。iPhone/iPad SDK 用的就是 Objective-C。 ‰‰ 其次,Objective-C 是标准 C 语言的一个超集,现存的 C 程序无须重新开发就能够使 用 iPhone/iPad 软件框架,并且在 Objective-C 中可以使用 C 的所有特性。可以选择什 么时候采用面向对象的编程方式(例如定义一个新的类),什么时候使用传统的面向 过程的编程方式(定义数据结构和函数)。 ‰‰ 此外,Objective-C 是一个简洁的语言,它的语法简单,没有歧义,易于学习。因为 术语易于混淆以及抽象设计的重要性,对于初学者来说,面向对象编程的学习曲线比 较陡峭。像 Objective-C 这种结构良好的语言使得成为一个面向对象程序员更为容易。 介绍 Objective-C 的章节也如同其语言本身一样简洁。 ‰‰ 和其他基于标准 C 语言的面向对象语言相比,Objective-C 对动态机制支持得更为彻底。 编译器为运行环境保留了很多对象本身的数据信息,因此某些在编译时需要做出的选 择就可以推迟到运行时来决定。这种特性使得基于 Objective-C 的程序非常灵活和强大。 4)基于 iPhone/iPad 的应用市场已经成为“成熟有价市场”。 苹果应用商店(App Store)是苹果公司为其 iPhone、iPod touch、iPad、Mac 等产品创 建和维护的数字化应用发布平台,允许用户从 iTunes Store 浏览和下载一些由 iOS SDK 或者 Mac SDK 开发的应用程序。根据应用发布的不同情况,用户可以付费或者免费下载。 V 如果你有不错的优秀产品,可以发布到苹果商店,标上价格。如果有人下载你的应用就 会很有信用地“付款”,达到一定的下载数量,也是一笔可观收入。国内有很多个人和小公 司,通过发布的应用已经获得了很大的成功。 本书适合哪些读者 ‰‰ 对软件开发,特别是对 iPhone/iPad 开发有兴趣的人。 ‰‰ 想成为一名专职的软件开发人员。 ‰‰ 在校的学生可将本书作为学习计算机软件开发的教程。 ‰‰ 想了解有关 iPhone/iPad 软件开发方面知识的从业人员,本书能帮你很快“过渡”。 ‰‰ 开设相关专业课程的大专院校。 你将学到什么 本书是 iPhone/iPad 开发的入门级书,也是系统介绍支持 iPhone/iPad 应用开发框架的书。 本书最大的作用就是引导你入门,只要你有梦想,有激情,通过本书你将会学到 iPhone/iPad 开发入门所需要掌握的基本知识。掌握本书知识,便可以迈进 iPhone/iPad 开发的门槛。 但是想成为一名优秀的 iPhone/iPad 软件开发人员,还需要很多方面的知识,所以希望 阅读完本书的你,继续深造学习,使自己“更上一层楼”! 你该如何阅读本书 本书采取循序渐进的方式。对于有一定的软件开发经验,或者缺乏耐心的读者,可以根 据“需求”选择不同的章节来阅读,这也是最有效的读书方式,也是我推崇的读书方式。 本书共 20 章,从内容上可以分四部分。 准备篇(第 1 ~ 2 章):首先介绍一些 iOS、Objective-C 和 Xcode 的入门知识,使读者 了解这三者之间的关系,粗略了解它们的发展史 ;并且关注了 iOS 7 的新特性。然后介绍了 大家熟悉的 Objective-C 应用开发环境的搭建,使读者初次感受 iPhone 开发过程。经过这部 分的学习使读者对有关 iPhone/iPad 开发有一个大致的了解,为以后应用开发做准备。 语法篇(第 3 ~ 8 章):主要介绍 Objective-C 语法知识,这部分内容有些枯燥,但却是 日后开发过程中必须掌握的基础知识。其中包括类、对象、消息和协议,以及内存管理。重 点介绍了 Foundation 框架常用类的使用方法。 基础篇(第 9 ~ 18 章):重点介绍 iOS 应用开发的框架,使读者掌握实际应用开发所需 要的基础知识,这一部分是本书的重点。通过本章的学习,读者应该掌握多状态和多任务、 视图和视图控制器、事件和通知、音频和视频。 实战篇(第 19 ~ 20 章):由于篇幅有限,实战环节只列出两个案例,分别是基于 iPad 的应用程序和基于 iPhone 的小游戏。通过两个案例,把前面学到的知识应用到实践中,使读 VI 者真正体验和了解软件开发的全过程。 勘误和支持 除封面署名外,参加本书编写工作的还有 :孙振江、陈连增、边伟、郭合苍、郑军、吴 景峰、杨珍民、王文朝、崔少闯、韦闪雷、刘红娇、王洁、于雪龙、孔琴。由于作者的水平 有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正。为 此,特意在此公布我的博客地址 http://blog.sina.com.cn/Beijingwolf。可以将书中的错误发布 在 Bug 勘误表页面中,如果你遇到任何问题可以留言,我将尽量在线上提供最满意的解答。 同时,我也会在博客上不定期发布一些实战示例,供读者下载学习,来不断强化所学知识。 该书定稿的时候,苹果陆续公布了大量有关 iOS 7 的技术文档和范例,且一直在进行 着。我会通过博客(http://blog.sina.com.cn/Beijingwolf)不定期发表有关 iOS 7 的最新材料, 紧跟 iOS 7 最新的技术进展,为各位读者提供完善的技术材料和指导。书中实例的源文件可 以从华章网站 下载,如果你有更多的宝贵意见,欢迎发送邮件至邮箱 yifeilang@aliyun.com, 期待能够得到你们的真挚反馈。1 致谢 我爱读书,往往不求甚解。中学时虽翻阅过《道德经》、《奇门遁甲》等书,但不得要领, 转瞬间,烟消云散。2012 年,到张瑞海先生的公司做事,有幸重温《道德经》。张先生是一位 令我“醍醐灌顶”的人,“慧根不深”但“后天上进,悟性很高”,不但创造了庞大事业,还引 导公司员工“禅悟”《道德经》。正是这个机会,使我悟到乔布斯在 iPhone/iPad 设计的“禅道”, 在冥冥中明白了“0”和“1”,与“道生一, 一生二, 二生三, 三生万物。万物负阴而抱阳,冲 气以为和”相通。明白写代码“天下之至柔”才是代码“重构”之道,程序的设计最高境界在 于“无为而无不为”。因有机会聆听先生之“布道”,使我掌握了 iPhone/iPad 设计之要领并写作 本书。如今能与诸位同仁共享本书,真心感激张先生这种独特的“布道、禅道和悟道”之术。 高山之巅,白云之上,羽翔之光。我甚幸遇到机械工业出版社华章公司杨福川先生,他 为我安排白宇女士作为本书的编辑。在编程行业,程序员都有一个过分的溺爱——容不得别 人“批评”自己写的东西,正如母亲爱护自己的孩子一样。之前,与其他出版社合作出版过 一本书,由于对方的编辑欠些“火候”,一本书将近一百个批注点,竟然只有三四个点“适得 要领”,虽然该书已出版,但我总感觉缺些什么。而白宇女士,俨然一位“武林”高手,熟谙 “点穴之法”,笔笔正中穴位之处,其批注使人心悦诚服,油然敬佩,为本书大增“光色”。 最后,感谢的就是你,我亲爱的读者,感谢你拿起这本书,你们的认可,就是我的最大 的快乐。 刘一道  华章网站 www.hzbook.com。 目  录 前言 第一部分 准备篇 第 1 章 初识 iOS、Objective-C 和     Xcode / 2 1.1 认识 iOS / 2 1.1.1 iOS 的发展历程 / 2 1.1.2 iOS 的设计和功能特性 / 3 1.1.3 iOS 7 的新特性 / 5 1.1.4 iOS 架构 / 6 1.1.5 iOS 框架 / 8 1.1.6 iOS 系统框架的变迁 / 11 1.1.7 Mac OS X 和 iOS 平台    不同框架的差异性 / 13 1.1.8 初步了解 iOS 开发者    工具 / 16 1.2 认识 Objective-C / 20 1.2.1 发展历程及版本变化 / 20 1.2.2 语言性能与分析 / 21 1.2.3 框架和代码的关系 / 22 1.3 基于非苹果机平台搭建开发    环境 / 23 1.3.1 前期准备 / 23 1.3.2 创建用于安装 Mac OS X    的 VMWare 虚拟机 / 24 1.3.3 安装 Mac OS X / 26 1.3.4 安装 Xcode SDK / 30 1.4 小结 / 33 第 2 章 创建你的第一个 iOS 应用     程序 / 34 2.1 应用程序的实现目标 / 34 2.2 入门的开始 / 35 2.2.1 新建一个 Xcode 项目 / 35 2.2.2 在模拟器中查看应用程序    的效果 / 37 2.3 启动一个应用程序 / 39 2.3.1 探究 main.m 源文件 / 40 2.3.2 分析属性列表文件 / 41 2.3.3 查看串联图 / 41 VIII 2.4 检查视图控制器及其视图 / 43 2.4.1 如何使用检查器 / 43 2.4.2 更改视图的背景颜色 / 45 2.5 对视图进行配置和管理 / 47 2.5.1 新增用户界面元素 / 47 2.5.2 为按钮增添一个动作 / 51 2.5.3 为文本框和标签创建    插座 / 53 2.5.4 打开 Connections 检查器    验证连接 / 56 2.5.5 对文本框进行委托    处理 / 57 2.5.6 让应用程序具有辅助    功能 / 58 2.6 使用视图控制器完成应用    程序 / 59 2.6.1 给用户的名称添加    属性 / 59 2.6.2 实现 changeGreeting:    方法 / 60 2.6.3 把视图控制器作为输入    文本框的委托 / 61 2.7 测试应用程序 / 62 2.7.1 排查和检测代码 / 62 2.7.2 程序代码清单 / 63 2.8 小结 / 64 第二部分 语法篇 第 3 章 Objective-C——构建 iOS 应用     程序的基石 / 66 3.1 探窥 Objective-C 语言 / 66 3.1.1 面向对象语言 Objective-C    是 C 语言的超集 / 67 3.1.2 类和对象 / 67 3.1.3 方法和发消息 / 69 3.1.4 属性和存取方法 / 71 3.1.5 块 / 72 3.1.6 协议和类别 / 73 3.1.7 类型和编码策略 / 74 3.1.8 import 语句 / 76 3.2 Objective-C 2.0 新增特性 / 78 3.2.1 关联引用 / 78 3.2.2 快速枚举 / 80 3.2.3 选择器 / 81 3.2.4 静态类型的使用 / 84 3.3 进一步认识块 / 87 3.3.1 块可以带参数和    返回值 / 87 3.3.2 块可以捕获封闭范围内    的值 / 87 3.3.3 在块内捕获变量值的    变化 / 88 3.3.4 块可以作为函数或者    方法的参数 / 89 3.3.5 用类型定义可以简化    块语法 / 90 3.3.6 使用属性可跟踪块 / 90 3.4 小结 / 91 第 4 章 类——构建应用程序的类型     对象原型 / 92 4.1 认识根类 / 92 4.1.1 NSObject 简介 / 92 4.1.2 根类和协议 / 93 4.1.3 根类方法 / 93 4.1.4 根类接口规范 / 95 4.1.5 根类实例方法和    类方法 / 95 4.2 如何设计类 / 95 4.2.1 设计接口 / 96 4.2.2 设计实现 / 99 IX 4.2.3 如何使用类名 / 100 4.2.4 如何比较类 / 101 4.3 类的类型 / 101 4.3.1 如何指定静态类型 / 101 4.3.2 类型的自查处理    机制 / 102 4.4 变量 / 102 4.4.1 局部变量 / 102 4.4.2 全局变量 / 103 4.4.3 实例变量 / 103 4.4.4 静态变量 / 106 4.4.5 变量的存储类别 / 108 4.5 属性 / 108 4.5.1 属性的声明 / 109 4.5.2 属性的实现 / 110 4.5.3 属性类型和相关    函数 / 111 4.5.4 属性的类型编码 / 112 4.5.5 属性重声明 / 112 4.5.6 修改父类的属性 / 113 4.5.7 新旧版本属性运行时    的区别 / 113 4.6 方法 / 114 4.6.1 方法通用格式 / 114 4.6.2 方法的调用 / 114 4.7 继承 / 115 4.7.1 类的继承关系 / 115 4.7.2 继承父类的实例    变量 / 116 4.7.3 继承父类的方法 / 116 4.7.4 哪些类需要被继承 / 117 4.7.5 对象的合成 / 118 4.8 方法重写 / 119 4.8.1 方法重写的规则 / 119 4.8.2 方法重写的使用 / 120 4.9 方法重载 / 121 4.9.1 方法重载的规则 / 121 4.9.2 方法重载的使用 / 121 4.9.3 调用还是重载 / 123 4.9.4 重载的类型 / 123 4.10 类的扩展 / 125 4.10.1 类别的用法 / 125 4.10.2 延伸的用法 / 127 4.11 异常处理 / 128 4.11.1 如何启用异常处理 / 128 4.11.2 如何捕捉不同类型的     异常 / 128 4.11.3 如何抛出异常 / 129 4.12 小结 / 129 第 5 章 对象——构建应用程序的重要     “活体” / 130 5.1 理解对象 / 130 5.1.1 对象和根类的关系 / 130 5.1.2 对象如何构建程序 / 131 5.1.3 对象动态类型 / 132 5.1.4 对象的生命周期 / 133 5.2 创建对象 / 137 5.2.1 对象分配处理的    机制 / 137 5.2.2 对象初始化方法的    原型 init / 137 5.2.3 初始化方法的    返回值 / 139 5.2.4 init 方法的实现 / 140 5.2.5 多个初始化方法和指定    初始化方法 / 141 5.2.6 使用 dealloc 方法 / 143 5.2.7 类工厂方法 / 144 5.3 对象的所有权 / 144 5.3.1 对象所有权策略有    哪些 / 145 X 5.3.2 保留计数的处理    机制 / 145 5.3.3 自动释放所有权 / 146 5.3.4 共享对象的有效性 / 147 5.3.5 如何获取所有权 / 148 5.4 回收对象 / 149 5.4.1 dealloc 方法的实现 / 149 5.4.2 通过引用返回的    对象 / 149 5.4.3 保留循环的处理    机制 / 150 5.4.4 对象的弱引用 / 150 5.4.5 资源的有效管理 / 151 5.5 应用对象 / 152 5.5.1 验证对象的功能 / 152 5.5.2 比较对象 / 153 5.5.3 复制对象 / 153 5.6 小结 / 154 第 6 章 消息和协议——对象之间的通信     方式 / 155 6.1 认识消息 / 155 6.1.1 消息的基本语法 / 155 6.1.2 消息处理机制 / 157 6.1.3 获得方法地址 / 158 6.1.4 使用隐藏的参数 / 159 6.2 消息发送 / 159 6.2.1 向 nil 发送消息 / 160 6.2.2 向自己发送消息 / 160 6.2.3 通过发送消息调用父类    方法 / 161 6.3 消息转发 / 163 6.3.1 消息转发处理机制 / 164 6.3.2 与多重继承关系 / 165 6.3.3 与类继承关系 / 166 6.3.4 与消息代理对象    关系 / 167 6.4 认识协议 / 167 6.4.1 协议的分类 / 167 6.4.2 协议对象 / 169 6.4.3 预定义接口 / 169 6.4.4 如何使用预定义    方法 / 170 6.4.5 为匿名对象声明    方法 / 171 6.5 应用协议 / 172 6.5.1 如何采用一个协议 / 172 6.5.2 如何服从一个协议 / 172 6.5.3 协议类型校验处理    机制 / 173 6.5.4 如何实现协议嵌套    协议 / 173 6.5.5 如何引用其他协议 / 174 6.6 小结 / 175 第 7 章 Foundation 框架——提供基本的     系统服务 / 176 7.1 认识 Foundation 框架 / 176 7.1.1 Foundation 类层次    结构 / 176 7.1.2 与 Core Foundation 框架    的区别 / 180 7.1.3 如何引用及查询    Foundation 框架信息 / 181 7.1.4 对象的可变性和    不变性 / 183 7.2 创建和使用值对象 / 184 7.2.1 创建值对象 / 184 7.2.2 字符串和 NSString 字面    常量 / 185 7.2.3 NSNumber 字面常量 / 185 7.2.4 日期和时间 / 186 7.3 创建和使用集 / 186 XI 7.3.1 将对象按某种顺序储存在    数组中 / 187 7.3.2 将键值对储存在    字典中 / 189 7.3.3 将无序对象储存在    集合中 / 190 7.4 Foundation 框架常用类的使用    方法详解 / 190 7.4.1 数字类型 / 191 7.4.2 字符串类型 / 193 7.4.3 数组类型 / 197 7.4.4 字典类型 / 205 7.4.5 日期类型 / 207 7.5 小结 / 209 第 8 章 内存管理——应用程序高效运行     的基础 / 210 8.1 内存管理基础知识 / 210 8.1.1 Cocoa 的引用计数    机制 / 210 8.1.2 内存管理规则 / 211 8.1.3 内存管理应用实例 / 211 8.1.4 内存管理混乱的原因及    解决方法 / 212 8.2 存取方法 / 214 8.2.1 声明存取方法 / 214 8.2.2 实现存取方法 / 214 8.2.3 存取方法的使用 / 216 8.2.4 实现重置方法 / 217 8.2.5 应用存取方法常见    错误 / 218 8.3 自动释放池 / 218 8.3.1 自动释放池工作    原理 / 219 8.3.2 非 Application Kit 程序中    的自动释放池 / 219 8.3.3 自动释放池和线程 / 220 8.3.4 作用域和嵌套自动释放池    的关系 / 220 8.3.5 所有权策略 / 221 8.3.6 如何实现垃圾回收 / 221 8.4 内存中复制的处理机制 / 222 8.4.1 深复制和浅复制 / 222 8.4.2 独立副本 / 223 8.4.3 使用 alloc 和 init 方式    复制 / 223 8.4.4 使用 NSCopyObject 函数    复制 / 224 8.4.5 可变对象和不可变对象的    复制 / 225 8.4.6 值对象和复制 / 226 8.5 Nib 对象的内存管理 / 226 8.5.1 插座对象实现的内存管理    机制 / 227 8.5.2 Nib 文件实现的内存管理    机制 / 227 8.6 小结 / 228 第三部分 基础篇 第 9 章 探究 iOS 应用程序的核心 / 230 9.1 iOS 应用程序核心架构 / 230 9.1.1 iOS 应用程序的生命    周期 / 230 9.1.2 iOS 应用程序的    主函数 / 230 9.1.3 应用程序的委托 / 232 9.1.4 主 Nib 文件 / 232 9.1.5 事件处理周期 / 232 9.1.6 应用程序的基本设置    模式 / 234 XII 9.2 iOS 应用程序的核心对象 / 235 9.2.1 iOS 应用程序的常见    对象 / 235 9.2.2 数据模型的定义 / 237 9.2.3 构建用户界面 / 239 9.3 iOS 应用程序包 / 241 9.3.1 典型的 iOS 应用程序    捆绑包 / 242 9.3.2 信息属性列表 / 243 9.3.3 程序图标和启动    图像 / 246 9.3.4 Nib 文件 / 246 9.4 iOS 应用程序关键任务的    处理机制 / 247 9.4.1 初始化和终止 / 247 9.4.2 响应中断 / 248 9.4.3 低内存警告 / 249 9.5 iOS 应用程序的行为定制 / 250 9.5.1 以景观模式启动 / 250 9.5.2 和其他应用程序的    通信 / 251 9.5.3 定制的 URL 模式 / 251 9.5.4 应用程序的偏好    设置 / 253 9.5.5 关闭屏幕锁定 / 254 9.6 小结 / 254 第 10 章 多状态和多任务——iOS 功能      日趋增强的表现 / 255 10.1 应用程序的状态 / 255 10.1.1 状态切换遵循的     原则 / 255 10.1.2 应用程序的状态及     切换路径 / 256 10.2 应用程序启动周期 / 257 10.2.1 加载进入前台 / 257 10.2.2 加载进入后台 / 258 10.2.3 主函数 / 260 10.2.4 启动处理的机制 / 260 10.3 响应中断 / 261 10.3.1 基于警告的中断处理     机制 / 261 10.3.2 中断应激处理 / 262 10.3.3 通话时用户界面的     调整处理 / 263 10.4 前台与后台之间的切换    机制 / 263 10.4.1 从前台切换到后台的     流程 / 263 10.4.2 转换后台时应激     处理 / 264 10.4.3 后台运行时的内存     情况 / 265 10.4.4 从后台回转到前台的     流程 / 265 10.4.5 在唤醒时处理排队的     通知 / 266 10.4.6 应用程序的终止     条件 / 266 10.5 主运行循环 / 267 10.5.1 主运行循环的运行     机制 / 267 10.5.2 iOS 应用程序中的     事件 / 268 10.6 后台执行和多任务处理 / 268 10.6.1 判断设备系统多任务     是否可用 / 269 10.6.2 在后台执行有限长度     的任务 / 269 10.6.3 调度本地通知的     递送 / 270 10.6.4 允许在后台运行的     任务 / 271 XIII 10.6.5 选择退出后台的     执行 / 271 10.7 小结 / 271 第 11 章 视图——iOS 应用程序交互的      基础 / 272 11.1 窗口、视图、视图    控制器 / 272 11.1.1 三者之间的关系 / 272 11.1.2 窗口 / 273 11.1.3 视图 / 273 11.1.4 视图控制器 / 274 11.2 视图的几何属性特征 / 275 11.2.1 视图坐标系统 / 275 11.2.2 边框、边界和中心的     关系 / 275 11.2.3 坐标系统变换 / 277 11.3 视图架构处理 / 277 11.3.1 视图交互模型 / 278 11.3.2 视图渲染架构 / 279 11.3.3 改变视图的层 / 280 11.3.4 内容模式与比例     缩放 / 280 11.3.5 自动尺寸调整     行为 / 281 11.4 视图的创建和管理 / 283 11.4.1 创建视图对象 / 284 11.4.2 视图的标识和     命名 / 284 11.4.3 子视图的添加和     移除 / 285 11.4.4 视图层次中的坐标     转换 / 287 11.4.5 视图的查询 / 288 11.5 在运行时修改视图 / 289 11.5.1 实现视图动画 / 290 11.5.2 配置动画的参数 / 290 11.5.3 配置动画的委托 / 291 11.5.4 响应布局的变化 / 291 11.5.5 重画视图的内容 / 292 11.5.6 隐藏视图 / 293 11.6 定制视图对象 / 293 11.6.1 初始化定制视图 / 293 11.6.2 绘制视图内容 / 294 11.6.3 响应事件 / 295 11.6.4 视图对象的清理 / 295 11.7 小结 / 296 第 12 章 UIKit 框架——创建基于触摸的      用户界面 / 297 12.1 UIKit 标准视图的分类 / 297 12.2 显示视图 / 298 12.2.1 标签 / 298 12.2.2 图片视图 / 299 12.2.3 进度条视图 / 300 12.2.4 等待视图 / 301 12.3 控件 / 302 12.3.1 按钮 / 302 12.3.2 文本框 / 304 12.3.3 滑块 / 305 12.3.4 切换开关 / 306 12.4 导航视图 / 307 12.4.1 导航栏 / 307 12.4.2 标签栏 / 309 12.5 警告视图和动作表单 / 310 12.5.1 警告视图 / 310 12.5.2 动作表单 / 311 12.6 文本和 Web 视图 / 313 12.6.1 文本视图 / 313 12.6.2 Web 视图 / 314 12.7 容器视图 / 315 12.7.1 表视图 / 315 XIV 12.7.2 滚动视图 / 319 12.7.3 工具栏 / 321 12.8 其他常用类型 / 322 12.8.1 分页控件 / 322 12.8.2 搜索栏 / 324 12.9 小结 / 328 第 13 章 视图控制器——视图的幕后      操纵者 / 329 13.1 视图控制器基础知识 / 329 13.1.1 视图控制器的     功能 / 329 13.1.2 视图控制器的     管理机制 / 330 13.1.3 视图控制器的     分类 / 331 13.1.4 视图控制器的内容     多种展示方式 / 333 13.1.5 多种视图控制器     混合应用 / 334 13.2 视图控制器的生命周期 / 335 13.2.1 视图控制器的     初始化 / 336 13.2.2 视图的加载和     卸载 / 336 13.3 标准视图控制器 / 338 13.3.1 标准视图控制器的     功能及使用方法 / 339 13.3.2 标准视图控制器的     应用 / 340 13.4 分割视图控制器 / 342 13.4.1 分割视图控制器构建     原理及功能 / 342 13.4.2 分割视图控制器的     应用 / 343 13.5 导航视图控制器 / 344 13.5.1 导航视图控制器构建     原理及功能 / 344 13.5.2 导航视图控制器的     应用 / 346 13.6 选项卡视图控制器 / 347 13.6.1 选项卡视图控制器     构建原理及功能 / 347 13.6.2 选项卡视图控制器的     应用 / 349 13.7 翻页视图控制器 / 349 13.7.1 翻页视图控制器构建     原理及功能 / 349 13.7.2 翻页视图控制器的     应用 / 351 13.8 小结 / 351 第 14 章 事件——应用程序的驱动      动力 / 352 14.1 iOS 系统中的事件 / 352 14.1.1 事件是如何驱动应用     程序的 / 352 14.1.2 什么是触摸事件 / 352 14.1.3 什么是运动事件 / 353 14.1.4 事件和触摸 / 354 14.1.5 事件的传递 / 356 14.1.6 响应者对象和     响应者链 / 356 14.1.7 调整事件的传递 / 357 14.2 常见手势的处理实例 / 358 14.2.1 触摸事件处理     方法 / 358 14.2.2 单个和多个触碰     手势处理方法 / 359 14.2.3 检测碰擦手势 / 360 14.2.4 处理复杂的多点     触摸序列 / 361 XV 14.2.5 触摸事件处理     技巧 / 362 14.3 键盘管理 / 363 14.3.1 接收键盘通告 / 363 14.3.2 显示键盘 / 365 14.3.3 取消键盘 / 365 14.3.4 移动键盘下面的     内容 / 365 14.4 小结 / 368 第 15 章 通知——消息的多样化      展示 / 369 15.1 通知的实现原理 / 369 15.1.1 本地通知 / 369 15.1.2 推送通知 / 370 15.1.3 通知的应用场景 / 371 15.2 通知的相关事务 / 373 15.2.1 自定义警告声音 / 373 15.2.2 创建和调度本地     通知 / 373 15.2.3 接收远程通知 / 375 15.2.4 如何处理通知 / 377 15.3 推送通知服务 / 380 15.3.1 推送通知和路径 / 380 15.3.2 服务的反馈和     质量 / 381 15.3.3 推送通知的安全处理     机制 / 381 15.3.4 通知的负载处理 / 385 15.4 推送通知的配置和开发 / 386 15.4.1 沙箱环境和产品     环境 / 386 15.4.2 配置处理流程 / 387 15.5 实现推送通知服务的通信    功能 / 389 15.5.1 普通提供的通信     功能 / 389 15.5.2 二进制接口和通知的     格式 / 390 15.5.3 服务的反馈 / 393 15.6 小结 / 393 第 16 章 Core Data 框架——管理应用      程序的数据模型 / 394 16.1 认识 Core Data / 394 16.1.1 初窥 Core Data     特性 / 394 16.1.2 Core Data 数据管理     机制 / 395 16.1.3 探究 Core Data 的     本质 / 396 16.2 Core Data 堆栈配置 / 396 16.2.1 托管对象和     上下文 / 398 16.2.2 读取请求 / 399 16.2.3 持久化存储     协调者 / 400 16.2.4 持久化存储 / 401 16.2.5 持久化文档 / 402 16.2.6 托管对象模型 / 402 16.3 Core Data 模块的运作机制及     基础类 / 402 16.3.1 NSManagedObject-     Context 类 / 403 16.3.2 NSManaged-     Object 类 / 404 16.3.3 NSManagedObject-     Context 类 / 405 16.3.4 NSPersistentStore-     Coordinator 类 / 405 16.3.5 NSPersistent-     Document 类 / 406 16.3.6 NSFetch-     Request 类 / 406 XVI 16.4 Core Data 堆栈访问    技术 / 407 16.4.1 创建一个新的托管     对象上下文 / 407 16.4.2 读取托管对象模型和     实体 / 407 16.4.3 添加持久化存储 / 407 16.5 托管对象的管理 / 408 16.5.1 读取托管对象 / 408 16.5.2 读取特定的     属性值 / 411 16.5.3 创建托管对象 / 412 16.5.4 保存托管对象 / 413 16.5.5 删除托管对象 / 413 16.6 小结 / 413 第 17 章 音频和视频——强大的多媒体      功能支持 / 414 17.1 初识 iOS 多媒体框架 / 414 17.1.1 iOS 声音处理     工具 / 414 17.1.2 Core Audio 框架 / 414 17.1.3 音频硬件编解码 / 415 17.1.4 iOS 支持的音频回放     和录制格式 / 415 17.1.5 Core Audio 音频会话     接口 / 416 17.1.6 iOS 系统支持的音频     单元 / 417 17.2 录制音频 / 417 17.2.1 使用 AVAudioRecorder     类进行录制 / 417 17.2.2 用音频队列服务进行     录制 / 418 17.2.3 音频中断处理 / 419 17.3 播放音频 / 419 17.3.1 通过 iPod 媒体库访问     接口播放媒体项 / 419 17.3.2 使用系统声音服务     播放短声音及触发     振动 / 420 17.3.3 通过 AVAudioPlayer     类轻松播放声音 / 421 17.3.4 用音频队列服务播放     和控制声音 / 423 17.3.5 使用 OpenAL 播放和     定位声音 / 426 17.3.6 解析音频流 / 426 17.4 iPhone/iPad 音频的最佳    实践 / 426 17.4.1 操作音频的贴士 / 426 17.4.2 iOS 中偏好的音频     格式 / 427 17.5 iOS 中的视频 / 428 17.5.1 录制视频 / 428 17.5.2 播放视频 / 428 17.6 小结 / 429 第 18 章 设备特性——支持多种功能的      应用 / 430 18.1 识别可用的硬件特性 / 430 18.2 External Accessory 框架的    管道机制 / 430 18.2.1 声明应用程序支持的     协议 / 431 18.2.2 在运行时连接     配件 / 431 18.2.3 处理和流相关的     数据 / 432 18.2.4 监控与配件有关的     事件 / 433 18.3 访问加速计事件 / 433 XVII 18.3.1 配置加速计 / 434 18.3.2 选择恰当的更新     频率 / 435 18.3.3 从加速计数据中分离     重力成分 / 435 18.3.4 从加速计数据中分离     实时运动成分 / 435 18.3.5 取得当前设备的     方向 / 436 18.4 Core Location 框架提供    定位服务 / 436 18.4.1 获取用户的当前     位置 / 436 18.4.2 获取方向相关的     事件 / 438 18.5 Map Kit 框架提供地图    服务 / 439 18.5.1 在用户界面中加入     地图视图 / 440 18.5.2 缩放和移动地图     内容 / 440 18.5.3 显示用户的当前     位置 / 441 18.5.4 坐标和像素之间的     转换 / 441 18.5.5 通过反向地理编码器     获取地标信息 / 441 18.6 地图上注解的实现 / 442 18.6.1 在地图视图上显示     注解 / 442 18.6.2 添加和移除注解     对象 / 443 18.6.3 定义注解视图 / 443 18.6.4 创建注解视图 / 443 18.6.5 处理注解视图中的     事件 / 444 18.7 UIKit 框架的应用 / 447 18.7.1 显示照相机界面 / 447 18.7.2 关闭照相机界面 / 448 18.7.3 显示照片选取器     界面 / 449 18.8 使用邮件编辑界面 / 450 18.9 小结 / 451 第四部分 实战篇 第 19 章 iPad 应用开发实战——精灵      小书柜 / 454 19.1 系统的总体设计方案 / 454 19.1.1 系统模块组成 / 454 19.1.2 启动项目 / 455 19.2 组装书柜 / 458 19.2.1 书柜的实现 / 459 19.2.2 书柜的框架及     组装 / 460 19.2.3 设置书柜的显示     效果 / 461 19.3 在小书柜上展示书的    封面 / 462 19.3.1 构建书的封面 / 462 19.3.2 将书的封面展示在     书柜中 / 463 19.3.3 构建多面书柜 / 463 19.3.4 通过手势展示多面     书柜 / 465 19.4 阅读书的内容 / 465 19.4.1 设计内容显示的     模板 / 465 19.4.2 书的封面与内容     关联 / 466 19.4.3 动画效果翻页 / 469 19.4.4 返回书柜主界面 / 470 XVIII 19.5 小结 / 471 第 20 章 iPhone 应用开发实战——弹球      游戏 / 472 20.1 认识游戏引擎 / 472 20.1.1 Cocos2D 游戏     引擎 / 472 20.1.2 Box2D 物理引擎 / 473 20.1.3 Chipmunk 物理     引擎 / 474 20.1.4 游戏引擎组成     结构 / 474 20.1.5 物理引擎中的     刚体 / 476 20.2 Cocos2D 框架中的    常用类 / 476 20.2.1 节点类 CCNode / 476 20.2.2 场景类 CCScene / 477 20.2.3 层类 CCLayer / 479 20.2.4 标签类 CCLabel / 480 20.2.5 动作类 CCAction / 481 20.2.6 导演类 CCDirector / 482 20.2.7 精灵类 CCSprite / 483 20.3 弹球游戏的准备工作 / 483 20.3.1 弹球游戏实现     目标 / 483 20.3.2 安装 Cocos2D-     iPhone / 484 20.3.3 启动项目的创建 / 485 20.3.4 程序控制权的     转交 / 486 20.4 实现场景及其互动    对象 / 489 20.4.1 添加自定义的游戏     场景 / 489 20.4.2 创建场景类     GameScene / 489 20.4.3 创建 World 对象和     边界框 / 491 20.5 制作弹球 / 492 20.5.1 添加弹球精灵 / 492 20.5.2 创建弹球 body 并     添加冲力 / 493 20.5.3 使用 tick 方法刷新     对象 / 493 20.6 设计球拍 / 494 20.6.1 增加球拍 / 494 20.6.2 移动球拍 / 496 20.6.3 限制球拍 / 497 20.6.4 球反弹的优化     处理 / 498 20.7 方块的实现 / 498 20.7.1 增加方块 / 499 20.7.2 销毁方块 / 500 20.8 游戏逻辑处理 / 501 20.8.1 碰撞检测机制 / 501 20.8.2 球碰到屏幕底部 / 502 20.8.3 游戏结束的条件 / 504 20.8.4 添加游戏结束     场景 / 505 20.9 添加游戏音乐 / 507 20.10 小结 / 507 第一部分 准 备 篇 ‰ 第 1 章 初识 iOS、Objective-C 和 Xcode ‰ 第 2 章 创建你的第一个 iOS 应用程序 第 1 章 初识 iOS、Objective-C 和 Xcode “问渠哪得清如许,为有源头活水来。”如果要开发 iPhone/iPad 应用程序,就不得不探 究其源头——iOS 和 Objective-C。本章将从整体的角度介绍 iOS 架构和常用框架,使你对基 于 iPhone/iPad 的应用开发,从整体轮廓上有一个清新的认识。由于 iOS 架构为开发 iPhone 和 iPad 应用提供了丰富而强大的功能,因此,了解架构里不同功能的框架是十分重要的。在 介绍 iOS 之余,初步认识一下 Objective-C;最后介绍如何搭建 iPhone/iPad 开发环境。 1.1 认识 iOS iOS 是由苹果公司(以下简称“苹果”)开发的手持设备操作系统。苹果公司在 2007 年 1 月 9 日的 MacWorld 大会上公布这个系统,随后于同年的 6 月发布了第一版 iOS 操作系统, 当初此操作系统的名称为“iPhone runs OS X”。最初设计是供 iPhone 使用的,后来陆续套用 到 iPod touch、iPad 以及 Apple TV 等苹果产品上。就像其基于的 Mac OS X 操作系统一样, 它也是以 Darwin 为基础的。系统操作占用约 240MB 的内存空间。 1.1.1 iOS 的发展历程 iOS 经过不断发展和完善,逐步被人们熟悉和接受。最初,由于没有人了解 iPhone runs OS X 的潜在价值和发展前景,导致没有一家软件公司、没有一个软件开发者为 iPhone runs OS X 开发软件或者提供软件支持。于是,苹果公司时任 CEO 斯蒂夫·乔布斯说服各大软件 公司以及开发者可以先搭建低成本的网络应用程序(Web APP),使得它们能像 iPhone 的本 地化程序一样测试 iPhone runs OS X 平台。 2007 年 1 月,苹果发布了 iPhone OS 1.0。该版本支持多点触控、虚拟键盘输入、邮件 发送等功能。 2008 年 7 月,苹果发布了 iPhone OS 2.0。版本 2.0 比 1.0 增加了很多新的功能。例如加 入了 App Store、支持手写输入、正式支持中文、支持 Office 文档、截图功能和计算器等。 2009 年 6 月,苹果发布了 iPhone OS 3.0。版本 3.0 比 2.0 增加了 100 多种功能,例如人 们期待的剪切、复制、粘贴、蓝牙等,以及手机横向键盘、彩信、P2P 连接等功能吸引了大 量的用户。 2010 年 6 月,苹果发布其第四代 iPhone 操作系统。由于获得了思科 iOS 的名称授权, 从 2010 年起,iPhone OS 正式更名为 iOS,该版本为 iOS 4。相比以前的版本,iOS 4 有多任 务处理、程序文件夹 E-mail 功能增强、企业级应用、游戏中心、广告嵌入和无分类邮箱等七 大改进,尤其是多任务处理,弥补了苹果长期以来的痛处。 第 1 章 初识 iOS、Objective-C 和 Xcode   3 2011 年 6 月,苹果发布了 iOS 5。相比 iOS 4,新版本增加了联系人黑名单,同时把 Twitter 和 Siri 也整合到了该系统。Siri 是基于 Nuance 的语音识别技术,它不是苹果土生土长 的服务,而是苹果 2010 年 4 月通过收购所得的。从此版本起,iOS 开始了增加第三方应用。 2012 年 6 月,苹果公司在 WWDC 2012 大会上公布了全新的 iOS 6 操作系统。iOS 6 可 算是苹果首次对深度整合的实施,Facebook、新浪微博、百度搜索、视频等第三方应用也不 断整合到新的版本中。 2013 年 6 月 10 日,苹果公司在 WWCD 2013 大会发布了全新 iOS 7 操作系统。iOS 7 完 全抛弃了以前的风格,重新设计了拨号、天气、日历、短信等应用的交互 界面,整体透露出 简洁、动感、时尚之感。图 1-1 为苹果公司最新发布的 iOS 7 界面。 图 1-1 iOS 7 界面 1.1.2 iOS 的设计和功能特性 iOS 是一款优秀、先进的移动操作系统,具有简单易用的界面、令人惊叹的功能,以 及超强的稳定性,已经成为 iPhone、iPad 和 iPod touch 的强大基础。尽管其他竞争对手一 直努力地追赶,但 iOS 内置的众多技术和功能让 Apple 设备始终保持着遥遥领先的地位。 总结 iOS 设计和功能特性,有以下几点。 1. 优雅直观的界面 第一次上手,你就会知道怎样使用你的 iPhone、iPad 和 iPod touch,如图 1-2 所示。因 为 iOS 中极具创新的 Multi-Touch 界面专为手指而设计。 前所未有的轻松体验从简洁美观的主屏幕开始。从内置App 到 App Store 提供的 700 000 多款 App 和游戏,从进行 FaceTime 视频通话,到用 iMovie 剪辑视频,你所触及的 一切,无不简单、直观、充满乐趣。 4   第一部分 准 备 篇 图 1-2 基于 iOS 的产品 2. 精彩功能与内置 App iOS 不断丰富的功能和内置 App,让 iPhone、iPad 和 iPod touch 比以往更强大、更具创 新精神,使用起来乐趣无穷。 3. 软硬件搭配如天作之合 由于 Apple 同时制造 iPad、iPhone 和 iPod touch 的硬件和操作系统,因此一切都配合得 天衣无缝。这种高度整合使 App 得以充分利用 Retina 显示屏、Multi-Touch 界面、加速感应 器、三轴陀螺仪、加速图形功能以及更多硬件功能。FaceTime 就是一个绝佳典范,它使用前 后两个摄像头,具有显示屏、麦克风和 WLAN 网络连接。 4. 世界级庞大的智能 App 集合 iOS 平台拥有数量庞大的移动 App,几乎每类 App 都有数千款,而且每款 App 都天生 出色。这是因为 Apple 为第三方开发者提供了丰富的工具和 API,从而让他们设计的 App 能充分利用每部 iOS 设备蕴含的先进技术。所有 App 都集中在一处,只要使用你的 Apple ID,即可轻松访问、搜索和购买这些 App。你需要做的,只是在设备上访问 App Store,然 后轻点下载。 5. 轻松更新 iOS 可以免费更新。有更新发布后,你可以通过无线方式将其下载到 iPhone、iPad 或 iPod touch 上。设备甚至可以适时提醒你下载最新的版本,使你不会错过最近更新的所有精 彩功能。 6. 安全可靠的设计 从你打开设备的那一刻起,iOS 就开始为你提供内置的安全性。我们专门设计的低层级 第 1 章 初识 iOS、Objective-C 和 Xcode   5 的硬件和固件功能,用以防止恶意软件和病毒 ;同时还提供高层级的 OS 功能,在访问个人 信息和企业数据时确保安全性。 为了保护用户的隐私,从日历、通讯录、提醒事项和照片获取位置信息的 App 必须先获 得用户的许可。用户可以设置密码锁,以防止有人未经授权访问自己的设备,还可以进行相 关配置,允许设备在多次尝试输入密码失败后删除所有数据。该密码还会为用户存储的邮件 自动加密和提供保护,并能允许第三方 App 为其存储的数据加密。iOS 支持加密网络通信, 用于保护 App 传输过程中的敏感信息。如果设备丢失或失窃,可以利用“查找我的 iPhone” 功能在地图上定位设备,并远程擦除所有数据。一旦 iPhone 失而复得,还能恢复上一次备份 的全部数据。 7. 内置辅助功能 引导式访问、VoiceOver 和 AssistiveTouch 功能,让更多的人可以体验 iOS 设备的迷人之 处。比如,凭借内置的 VoiceOver 屏幕阅读技术,视力不佳的人可以听到其手指在屏幕上触 摸到的项目说明。iOS 开箱即可支持 30 多种无线盲文显示屏,还能提供许多备受赞誉的辅助 功能,如动态屏幕放大、隐藏式字幕视频播放、单声道音频、黑底白字显示等。 8. 世界通用 iOS 设备可在世界各地通用。30 多种语言供你挑选,可以在各种语言之间轻松切换。 iOS 键盘基于软件而设计,有 50 多种支持特定语言功能的不同版式供你选择,其中包括字符 的变音符和日文关联字符选项。此外,内置词典支持 50 多种语言,VoiceOver 可阅读超过 35 种语言的屏幕内容,语音控制功能可读懂 20 多种语言。 9. 适合不同阶层人士使用 对于商务使用,全球的企业都开始选用 iOS 设备,因为它具有企业专属功能和高度的安 全性。iOS 兼容 Microsoft Exchange 和标准服务器,可发送无线推送的电子邮件、日历和通 讯录。iOS 在传输、设备内等待和 iTunes 备份三个不同阶段为信息分别加密,确保数据安全。 用户可以安全地通过业界标准 VPN 协议接入私人企业网络,公司也可以使用配置文件轻松 地在企业内部署 iPhone。 对于学习之用,有了 iOS,iPhone、iPad 和 iPod touch 即可变为出色的学习工具。用户 可使用日历来追踪所有的课程和活动,提醒自己准时赴约并参加小组学习,还可利用备忘录 App 随手记下清单内容,或将好想法写下来。借助内置 WLAN 功能在网上进行研究或撰写 电子邮件,甚至还可以添加照片或文件附件 ;使用语音备忘录功能录制采访、朗读示例、学 习指南或课堂讲座。无论是单词定义、练习法语词汇,还是查找腰脊柱的位置,都能在 App Store 找到相应的 App。 1.1.3 iOS 7 的新特性 iOS 7 是苹果公司推出最新一代的 iOS 操作系统,图 1-3 列出了 iOS 7 的新特性。 6   第一部分 准 备 篇 图 1-3 iOS 7 新特性 iOS 7 最大的改进在于重新设计的UI 界面更加小清新,更加绚丽。此外还借鉴了 Android 操作系统的诸多优势,使 iOS 7 更加接地气、人性化。全新的界面设计彻底颠覆了 延续近 6 年的“乔帮主风格”,让人眼前一亮,苹果称其为 iOS 诞生以来最大的一次改变。 iOS 7 中去掉了诸多拟物设计,而是采用更加简约,更加扁平化的图标设计。 除了界面图标改变之外,iOS 7 的新增功能也不少,例如多任务处理、共享处理和车载 等功能。多任务处理始终是在 App 之间切换的捷径。现在,它变得更加智能。因为 iOS 7 会 了解个人喜欢何时使用 App,并在个人启动 App 之前更新个人的内容。iOS 7 共享处理主要 依赖于 AirDrop,通过文本消息或电子邮件发送照片或文档是没问题的。但如果某人就在旁 边,文本消息或电子邮件会让人感觉大费周章。AirDrop 能快速、轻松地共享照片、视频、 通讯录,以及任何 App 中的一切。只需轻点共享按钮,然后选择共享对象即可。AirDrop 使 用无线网络和蓝牙,无须设置,而且传输经过加密,可严格保障共享内容的安全。车载 iOS 将个人的 iOS 设备,与个人的仪表盘系统无缝结合。如果个人的汽车配备车载 iOS,个人就 能连接 iPhone 5,并使用汽车的内置显示屏和控制键或 Siri 免视功能与之互动。现在,个人 可以轻松、安全地拨打电话、听音乐、收发信息、使用导航,以及更多。所有的设计,都为 了让 iPhone 专注于个人的需求,让司机可以专注于个人的驾驶。 iOS 7 对此前的“找回 iPhone”功能进行了补充 ;内置了一款图片管理软件,支持众多 滤镜效果,还可以用多种方式来浏览图片。 1.1.4 iOS 架构 iOS 架构和 Mac OS 的基础架构相似。站在高级层次来看,iOS 扮演底层硬件和应用程 序(显示在屏幕上的应用程序)的中介,如图 1-4 所示。用户创建的应用程序不能直接访问 硬件,而需要和系统接口进行交互。系统接口转而又去和适当的驱动打交道。这样的抽象可 第 1 章 初识 iOS、Objective-C 和 Xcode   7 以防止应用程序改变底层硬件。 图 1-4 应用程序位于 iOS 上层 注意 虽然应用程序通常会和底层硬件隔离,但是应用程序代码仍需考虑设备之间的某些差 异。举个例子,iPad 和 iPod touch 不能打开包含电话号码的 URL,但是 iPhone 却可以。 iOS 实现可以看做多个层的集合,底层为所有应用程序提供基础服务,高层则包含一些 复杂的服务和技术。图 1-5 为 iOS 架构层次。 图 1-5 iOS 架构层次 1. Cocoa Touch 层 Cocoa Touch 层包含创建 iOS 应用程序所需的关键框架。上至实现应用程序可视界面, 下至与高级系统服务交互,都需要该层技术提供底层基础。在开发应用程序的时候,尽可能 不要使用更底层的框架,尽可能使用该层的框架。Cocoa Touch 层支持多任务、数据保护、 推送通知服务、本地通知和手势识别器等高级特性。 2. Media 层 Media 层包含图形技术、音频技术和视频技术,这些技术相互结合就可为移动设备带来 8   第一部分 准 备 篇 最好的多媒体体验,更重要的是,它们让创建外观音效俱佳的应用程序变得更加容易。可以 使用 iOS 的高级框架更快速地创建高级的图形和动画,也可以通过底层框架访问必要的工 具,从而以某种特定的方式完成某种任务。 3. Core Services 层 Core Services 层为所有的应用程序提供基础系统服务。可能应用程序并不直接使用这些 服务,但它们是系统很多部分赖以建构的基础。 4. Core OS 层 Core OS 层的底层功能是很多其他技术的构建基础。通常情况下,这些功能不会直接应 用于应用程序,而是应用于其他框架。但是,在直接处理安全事务或和某个外设通信的时 候,必须要应用到该层的框架。 注意 在编写代码的时候,应该尽可能地使用高层框架,而不要使用底层框架。高层框架为 底层构造提供面向对象的抽象。这些抽象可以减少需编写的代码行数,同时还对诸如 Socket 和线程这些复杂功能进行封装,从而让编写代码变得更加容易。虽说高层框架是对底层构造 进行抽象,但是它并没有把底层技术屏蔽起来。如果高层框架没有为底层框架的某些功能提 供接口,开发者可以直接使用底层框架。 1.1.5 iOS 框架 应用程序由代码和 Apple 提供的框架组成,其关系如图 1-6 所示。框架包含了框架资源 库供应用程序调用,多个应用程序可同时访问一个框架资源库。开发的应用程序都会链接多 种框架,并通过框架的应用编程接口(API)来利用框架。API(已发布在头文件中)指定可 用的类、数据结构和协议。Apple 编写的框架,预计可能想要实现的基本功能。使用框架既 省时省力,又可确保代码高效、安全。系统框架是访问底层硬件的唯一途径。 图 1-6 代码、框架和应用程序之间的关系 框架是一个目录,包含了共享资源库,用于访问该资源库中储存的代码的头文件,以及 图像、声音文件等其他资源。共享资源库定义应用程序可以调用的函数和方法。 iOS 提供了许多可在应用程序开发中使用的框架。要使用一个框架,需要将它添加到 第 1 章 初识 iOS、Objective-C 和 Xcode   9 项目,以便应用程序可以链接到它。大多数应用程序都链接到 Foundation、UIKit 和 Core Graphics 框架。根据用户为应用程序选取的模板,可能也包括其他框架。如果一组核心框架 无法满足应用程序的要求,用户总是可以将其他框架添加到项目。 iOS 架构中包含了大量应用于不同领域的框架。对于Foundation、UIKit、Core Data、 Core Graphics、Core Animation 和 OpenGL ES 等常用框架,将会在本节中做简单介绍。其中 Foundation 和 UIKit 框架,能满足大多数应用程序开发的需求,但是在实际的应用程序开发 中,还要用到很多高级技术,这就要求我们对于 Core Data、Core Graphics、Core Animation 和 OpenGL ES 框架也要熟练掌握,以便于在开发应用程序中使用这些框架提供的高级技术。 1. Foundation 框架 Foundation 框架为所有应用程序提供基本的系统服务。应用程序以及 UIKit 和其他框架, 都建立在 Foundation 框架的基础结构之上。 Foundation 框架提供许多基本的对象类和数据类型,使其成为应用程序开发的基础。它 还制定了一些约定(用于取消分配等任务),使代码更加一致,可再用性更好。 Foundation 框架的功能如下: ‰‰ 创建和管理集,如数组和字典; ‰‰ 访问储存在应用程序中的图像和其他资源; ‰‰ 创建和管理字符串; ‰‰ 发布和观察通知; ‰‰ 创建日期和时间对象; ‰‰ 自动发现 IP 网络上的设备; ‰‰ 操控 URL 流; ‰‰ 异步执行代码。 通过以上简单介绍可以知道 Foundation 框架的重要性,后面的章节会专门进行详细介绍。 2. UIKit 框架 UIKit 框架提供的类可用于创建基于触摸的用户界面。所有 iOS 应用程序都基于 UIKit。 没有这个框架,就无法交付应用程序。 UIKit 提供基础结构用于在屏幕上绘图、处理事件,以及创建通用用户界面元素。UIKit 还通过管理屏幕上显示的内容来组织复杂的应用程序。 UIKit 框架的功能如下: ‰‰ 构建和管理用户界面; ‰‰ 处理基于触摸和运动的事件; ‰‰ 显示文本和网页内容; ‰‰ 优化应用程序以实现多任务; ‰‰ 创建自定用户界面元素。 UIKit 框架提供了大量用于设计用户界面交互的常用控件,它封装的类是开发可视化应 10   第一部分 准 备 篇 用程序不可缺少的,对于入门者来说,掌握 UIKit 是快速掌握 iOS 应用程序开发的捷径之 一。后面的章节将会专门详细介绍 UIKit 框架提供的一些常用类的用法。 3. Core Data 框架 Core Data 框架用于管理应用程序的数据模型。借助 Core Data,可以创建模型对象(称 为被管理的对象),管理那些对象之间的关系,并通过框架更改数据。Core Data 利用内建的 SQLite 技术高效地储存和管理数据。 Core Data 框架的功能如下: ‰‰ 存储对象和从储存处取回对象; ‰‰ 支持基本的撤销 / 重做; ‰‰ 自动验证属性值; ‰‰ 对内存中的数据进行过滤、分组和整理; ‰‰ 使用 NSFetchedResultsController 管理表格视图中的结果; ‰‰ 支持基于文稿的应用程序。 4. Core Graphics 框架 高质量的图形,是所有 iOS 应用程序的一个重要组成部分。使用 Core Graphics 框架可 以创建图形。在 iOS 中创建图形最简易、便捷的方法,是将预渲染的图像与 UIKit 框架的标 准视图和控制配合使用,并让 iOS 完成绘图。 由于 UIKit 提供用于自定绘图的类,包括路径、颜色、图案、渐变、图像、文本和变 换,因此建议尽可能地使用 UIKit(较高级的框架),而非 Core Graphics(较低级的框架)。 编写在 iOS 和 OS X 之间直接共享的绘图代码时,需要使用 Core Graphics。Core Graphics 框架也称为 Quartz,它在这两个平台上几乎相同。 Core Graphics 框架的功能如下: ‰‰ 制作基于路径的绘图; ‰‰ 使用边缘模糊化渲染; ‰‰ 添加渐变、图像和颜色; ‰‰ 使用坐标空间变换; ‰‰ 创建、显示和解析 PDF 文稿。 5. Core Animation 框架 使用Core Animation 框架可以制作高级动画和视觉效果。UIKit 提供的动画是建立 在 Core Animation 技术之上的。如果需要超出 UIKit 功能的高级动画,可以直接使用 Core Animation。 借助 Core Animation 能够创建不同层次的层对象,并对它们进行操控、旋转、缩放、变 换等。通过使用大家所熟悉的 Core Animation 视图式抽象,可以创建动态用户界面,而无须 使用低级的图形 API,如 OpenGL ES 等。 第 1 章 初识 iOS、Objective-C 和 Xcode   11 Core Animation 框架的功能如下: ‰‰ 创建自定动画; ‰‰ 给图形添加时序功能; ‰‰ 支持关键帧动画; ‰‰ 指定图形布局约束; ‰‰ 将多层更改分组为原子更新。 6. OpenGL ES 框架 OpenGL ES 框架提供2D 和 3D 绘图工具,支持基础的2D 和 3D 绘图。Apple 实施的 OpenGL ES 标准与设备硬件紧密协作,为全屏幕游戏类应用程序提供很高的帧速率。 OpenGL ES 框架的功能如下: ‰‰ 创建 2D 和 3D 图形; ‰‰ 制作更复杂的图形,如数据可视化、飞行模拟或视频游戏; ‰‰ 访问底层图形硬件。 1.1.6 iOS 系统框架的变迁 iOS 系统包含的框架为编写 iOS 平台的软件提供必要的接口。表 1-1 列出了框架中的 类、方法、函数、类型以及常量使用的关键前缀,请避免在符号名称中使用这些前缀。描述 iOS 设备提供的框架位于/Platforms/iPhoneOS.platform/Developer/SDKs// System/Library/Frameworks 目录。路径中的 表示 Xcode 的安装目录, 表 示目标 SDK 版本。表中“最先引入 iOS 版本”的列表示首次引入相关框架的 iOS 系统版本。 表 1-1 系统中的框架 框架名称 最先引入 iOS 版本 前缀 描  述 Accelerate.framework 4.0 cblas,vDSP 包含加速数学和 DSP 函数 Accounts.framework 5.0 AC 包含用于处理用户访问系统账户的接口 AddressBook.framework 2.0 AB 包含直接访问用户联系人数据库的函数 AddressBookUI.framework 2.0 AB 包含显示系统定义的联系人挑选界面和编辑界面的类 AdSupport.framework 6.0 AS 包含用于收集分析的类 AssetsLibrary.framework 4.0 AL 包含显示用户照片和视频的类 AudioToolbox.framework 2.0 AU,Audio 包含处理音频流数据以及播放或录制音频的接口 AudioUnit.framework 2.0 AU,Audio 包含加载并使用音频单元的接口 AVFoundation.framework 2.2 AV 包含播放或录制音频的 Objective-C 接口 CFNetwork.framework 2.0 CF 包含通过 Wi-Fi 或者蜂窝无线访问网络的接口 CoreAudio.framework 2.0 Audio 包含 Core Audio 框架使用的各种数据类型 CoreBluetooth.framework 5.0 CB 提供了用于访问低功耗的蓝牙硬件的接口 CoreData.framework 3.0 NS 包含管理应用程序数据模型的接口 12   第一部分 准 备 篇 框架名称 最先引入 iOS 版本 前缀 描  述 CoreFoundation.framework 2.0 CF 提供一些基本软件服务,包括常见数据类型抽象、 字符串实用工具、群体类型实用工具、资源管理以及 偏好设置 CoreGraphics.framework 2.0 CG 包含 Quartz 2D 接口 CoreImage.framework 5.0 CI 包含用于处理视频和图像的接口 CoreLocation.framework 2.0 CL 包含确定用户方位信息的接口 CoreMedia.framework 4.0 CM 包含操作音频和视频的底层例程 CoreMIDI.framework 4.2 MIDI 包含用于处理 MIDI 数据的低级别例程 CoreMotion.framework 4.0 CM 包含访问加速度计以及陀螺仪的数据的接口 CoreTelephony.framework 4.0 CT 包含访问电话相关的信息的例程 CoreText.framework 3.2 CT 包含一个文本的布局渲染引擎 CoreVideo.framework 4.0 CV 包含操作音频和视频的底层例程(不要直接使用) EventKit.framework 4.0 EK 包含访问用户日历事件数据的接口 EventKitUI.framework 4.0 EK 包含显示标准系统日历界面的类 ExternalAccessory.framework 3.0 EA 包含与外设进行通信的接口 Foundation.framework 2.0 NS 包含 Cocoa Foundation 层的类和方法 GameKit.framework 3.0 GK 包含点对点连接管理接口 GLKit.framework 5.0 GLK 包含用于构建复杂 OpenGL ES 应用程序的 Objective-C 的实用工具类 GSS.framework 5.0 GSS 提供一套标准的安全相关的服务 iAd.framework 4.0 AD 包含在应用程序中显示广告的类 ImageIO.framework 4.0 CG 包含读取或写入图像数据的类 IOKit.framework 2.0 N/A 包含设备所使用的接口。请不要直接使用此框架 MapKit.framework 3.0 MK 包含将地图界面嵌入到应用程序的类,也可以用于 查找地理编码反向坐标 MediaPlayer.framework 2.0 MP 包含显示全屏视频的接口 MediaToolbox.framework 6.0 MT 包含一些播放音频的接口 MessageUI.framework 3.0 MF 包含撰写和排队发送电子邮件信息的界面 MobileCoreServices.framework 3.0 UT 定义系统支持的统一类型标识符(UTIs) NewsstandKit.framework 5.0 NK 提供下载后台的杂志和报纸内容的接口 OpenAL.framework 2.0 AL 包含 OpenAL 接口(一个跨平台的方位音频库) OpenGLES.framework 2.0 EAGL,GL 包含 OpenGL ES 接口。OpenGL ES 框架是 OpenGL 跨 2D 和 3D 渲染库的跨平台版本 PassKit.framework 6.0 PK 包含用于创建数码取代的东西,如机票、登机牌、 会员卡、通行证的接口 QuartzCore.framework 2.0 CA 包含 Core Animation 接口 (续) 第 1 章 初识 iOS、Objective-C 和 Xcode   13 框架名称 最先引入 iOS 版本 前缀 描  述 QuickLook.framework 4.0 QL 包含预览文件接口 Security.framework 2.0 CSSM, Sec 包含管理证书、公钥、私钥以及信任策略的接口 Social.framework 6.0 SL 包含与社交媒体账户的接口 StoreKit.framework 3.0 SK 包含用于处理与应用程序内购买相关的财务交易 SystemConfiguration.framework 2.0 SC 包含用于处理设备网络配置的接口 Twitter.framework 5.0 TW 包含一些通过 Twitter 服务发送 Twitter 的接口 UIKit.framework 2.0 UI 包含 iOS 应用程序用户界面层使用的类和方法 VideoToolbox.framework 6.0 N/A 包含设备所使用的接口。但不包括这个框架 1.1.7 Mac OS X 和 iOS 平台不同框架的差异性 虽然 iOS 的大多数框架同样存在于 Mac OS X 系统中,但不同平台框架具有不同的实 现方式和使用方式。本节收集了一些开发 iOS 应用程序需要注意的重要差别。 1. UIKit 框架与 AppKit 框架的差异性 在 iOS 系统中,创建图形应用程序、管理事件循环以及执行其他界面相关的任务都离不 开 UIKit 提供的基础结构。UIKit 和 AppKit 具有非常显著的区别,在设计 iOS 应用程序的时 候,应该特别注意这一点。也正是这个原因,在将 Cocoa 应用程序迁移到 iOS 系统的时候, 必须提供和界面相关的类和逻辑。表 1-2 列出了框架之间的差异,可帮助你理解 iOS 中的应 用程序应该具有什么特征。 表 1-2 UIKit 框架与 AppKit 框架的差异性比较 差 异 点 差 异 性 文档支持 在 iOS 系统中,文档角色的重要性有所降低,简单内容模型则变得越来越重 要。因为 iOS 系统的应用程序通常只拥有一个窗口(在不连接外部显示的情况 下),主窗口是创建及编辑所有应用程序内容的唯一环境。更重要的是,所有和 文档相关的操作,包括文件的创建和管理,现在都由应用程序在幕后完成,不 再需要用户干预 视图类 UIKit 提供一组非常有针对性的视图和控件。AppKit 框架有许多视图和控件 无法在 iOS 设备上工作,其他一些视图则被更具 iOS 特色的视图替代。例如, 在显示分层信息的时候,iOS 不使用 NSBrowser 类,而是使用完全不同的样式 (导航控制器) 视图坐标系统 iOS 系统的 Quartz 和 UIKit 内容的绘画模型和 Mac OS X 的基本相同,只有 一处例外。在 Mac OS X 的绘画模型坐标系统中,窗口和视图的原点默认位于 左下角,坐标轴向上向右延伸。但在 iOS 系统中,默认的原点位置是左上角, 坐标轴向下向右延伸。Mac OS X 的坐标系统称为“被翻转”的坐标系统,iOS 则是默认坐标系统 (续) 14   第一部分 准 备 篇 差 异 点 差 异 性 窗口即视图 从概念上来看,iOS 系统的窗口和视图与 Mac OS X 的具有相同含义。但从实 现的角度来看,区别很大。在 Mac OS X 系统中,NSWindow 类是 NSResponder 类的子类,但在 iOS 系统中,UIWindow 实际是 UIView 的子类。继承关系上 的改变表明窗口将会使用 Core Animation 层来绘制外表。之所以有这样的改变, 主要是为了在操作系统级别支持窗口分层。举个例子,系统可以在一个独立的 窗口中显示状态栏,并让该窗口浮动于应用程序窗口之上 事件处理 iOS 系统和 Mac OS X 系统的另外一个差异和窗口的使用方式相关。Mac OS X 应用程序可以用于任意数量的窗口,但大多数 iOS 应用程序只能有一个窗口。在 iOS 应用程序中显示不同屏幕的数据不是通过改变窗口实现的,而是通过在应 用程序窗口中切换定制视图来完成的 目标 - 动作模型 UIKit 的事件处理模型和 Mac OS X 的事件处理模型区别很大。UIKit 框架不 向视图发送鼠标和键盘事件,而是发送触摸和移动事件。这些事件不但要求用 户实现一组不同的方法,同时也要求用户修改整个事件处理代码。举个例子, 本地跟踪循环的排队事件不能包含触摸事件,用户的代码也据此做相应调整 绘画及打印支持 UIKit 支持三种形式的动作,AppKit 仅支持一种。UIKit 的控件可以在不同的 交互阶段调用唤醒不同动作,而且一个交互过程可以指定多个目标。因此,在 UIKit 中,一个控件可以在一次交互过程中向多个目标发送多个不同的动作 文本支持 为支持 UIKit 渲染需要,UIKit 的绘画能力经过适当的调节。它支持图片的加 载和显示、字符串显示、颜色管理、字体管理,以及多个用于渲染矩阵和获取 图形上下文的函数。UIKit 不包含通用目的的绘图类,因为 iOS 系统使用其他方 式完成此类功能(即 Quartz 和 OpenGL ES) 存取方法的使用和属性对比 iOS 系统不支持打印功能,iOS 设备不能连接打印机或其他相关的打印硬件 控件和单元 撰写电子邮件和记事本是 iOS 系统提供的主要的文本支持。UIKit 类可以让应 用程序显示并编辑简单的字符串和稍微复杂点的 HMTL 内容 表视图 在 iOS 3.2 及以上系统中,Core Text 框架和 UIKit 框架提供更加精密的文本处 理能力,可以通过它们实现更精密的文本编辑及展现视图,也可通过它们定制 视图提供的输入方法 菜单 UIKit 在其类声明中大量使用属性。属性在Mac OS X 10.5 版本引入,是 AppKit 框架大量的类创建出来以后才出现的。属性不是对 AppKit 框架 getter 和 setter 方法的简单模仿,而是被 UIKit 用于简化类接口 Core Animation 层 UIKit 控件不使用单元。单元被Mac OS X 作为视图的轻量级替代物。而 UIKit 视图本身就是非常轻量的对象,因此单元派不上用场。虽然在命名约定 上,UITableView 类也用到单元这个词,但是此处的单元实际上是 UITableView 的子类 2. Foundation 框架的差异性 Mac OS X 和 iOS 都有 Foundation 框架,大多数所预期的类都可以在该框架中找到。两 个平台的框架都支持数值管理、字符串、集合、线程,以及许多其他常见的数据类型。 表 1-3 列出一些不包含于 iOS 框架的重要功能以及相关类不存在的缘由,同时尽量列出有哪 些技术可作为替代。 (续) 第 1 章 初识 iOS、Objective-C 和 Xcode   15 表 1-3 Mac OS X 和 iOS 在 Foundation 框架的比较 差 异 点 差 异 性 元数据和预测管理 iOS 不支持 Spotlight 元数据和搜索预测,因为 iOS 不支持 Spotlight 分布式对象和端口名称服务管理 iOS 不存在分布式对象技术,但是可以使用 NSPort 家族类和端口(及 socket) 进行交互。也可以使用 Core Foundation 和 CFNetwork 框架处理网络需求 Cocoa 绑定 iOS 不支持 Cocoa 绑定,而是使用经过少量修改的目标 - 动作模型。因为 这种方式可以让代码对动作的处理方式更有灵活性 Objective-C 垃圾收集 iOS 不支持垃圾收集,必须使用内存管理模型。需要通过保持对象来宣告 对对象的拥有权,并在不需要对象的时候释放对象 AppleScript 支持 iOS 不支持 AppleScript iOS 系统的 Foundation 框架提供对 XML 的支持,可以通过 NSXMLParser 类解析 XML 文件,其他解析类(包括 NSXMLDocument、NSXMLNode)不被支持。除了 NSXMLParser 之外,还可以使用 libXML2 库,这是 C 语言的 XML 解析接口。 3. 其他框架的差异性 Mac OS 和 iOS 系统中的其他框架的差异如表 1-4 所示。 表 1-4 同时存在于 iOS 和 Mac OS X 的框架之间的差异 框  架 差 异 性 AddressBook.framework 该框架接口可用于访问用户的联系人信息。虽然名称相同,但是此框 架的 iOS 版本和 Mac OS X 版本却有很大的区别。在 iOS 系统中,除了 访问联系人数据的 C 接口,还可以使用 Address Book UI 框架提供的类 展现标准联系人挑选和编辑界面 AudioToolbox.framework AudioUnit.framework CoreAudio.framework 在 iOS 系统中,这些框架支持音频录制、播放以及单声道和多声道的音 频内容混合,但不支持更高级的音频处理功能和定制音频单元插件。不过 iOS 系统增加了一个功能,即触发 iOS 设备(具有相应硬件)的振动功能 CFNetwork.framework 该框架包含 Core Foundation Network 接口。在 iOS 系统中,CFNetwork 框架是顶层框架,它没有子框架 CoreGraphics.framework 该框架包含 Quartz 接口。在 iOS 系统中,Core Graphics 框架是顶层 框架,它没有子框架。使用 Quartz 创建路径、渐变、阴影、图案、图 像以及位图的方式和 Mac OS X 系统完全相同。不过有一些 Quartz 的 功能(包括 PostScript 支持、图像来源和去向、Quartz 显示服务支持、 Quartz 事件服务支持)不存在于 iOS 系统中 OpenGLES.framework OpenGL ES 是专为嵌入式系统设计的OpenGL 版本。如果你是 OpenGL 开发人员,则应该会很熟悉 OpenGL ES 接口。不过,OpenGL ES 接口还是有几点较大差别。首先,它是一套更加小巧的接口,仅支 持可以在现有图形硬件上有效执行的功能。第二,许多桌面 OpenGL 可 以使用的扩展并不存在于 OpenGL ES。虽然如此,应该还是能够执行 大多数和桌面 OpenGL 相同的操作。但如果你是在迁移现有的 OpenGL 代码,可能需要重写一部分代码,使用 iOS 系统的渲染技术(不同于 Mac OS X) 16   第一部分 准 备 篇 框  架 差 异 性 QuartzCore.framework 该框架包含 Core Animation 接口。iOS 的大部分 Core Animation 接口 和 Mac OS X 相同。但是 iOS 系统没有用于管理布局约束的类,也不支 持使用 Core Image 过滤器。另外,iOS 也没有 Core Image 和 Core Video 接口(两者都包含于 Mac OS X 版本的 Quartz Core 框架中) Security.framework 该框架包含安全接口。在 iOS 系统中,该框架通过加解密、伪随机数 生成,以及 Keychain 保护应用程序数据安全。该框架不包含身份验证 或身份验证接口,也不支持显示证书内容。Keychain 接口是 Mac OS X 的简化版本 SystemConfiguration.framework 该框架包含和网络相关的接口。在 iOS 系统中,可以使用这些接口来 决定设备是通过 EDGE、GPRS,还是通过 Wi-Fi 与网络连接 1.1.8 初步了解 iOS 开发者工具 如果希望在 iOS 系统中开发应用程序,需要配备一台运行 Xcode 工具的 Mac OS X 计算 机。Xcode 是苹果公司的开发工具套件,可用于管理工程、编辑代码、构建可执行文件,还 可进行源码级调试、源代码仓库管理、性能调节等。套件的核心是 Xcode 应用程序本身,它 用于提供基本的源代码开发环境。但是 Xcode 并非唯一可以使用的工具,下面将介绍开发 iOS 软件使用的一些工具。 1. Xcode Xcode 是一个集成开发环境(IDE),可以用于创建及管理 iOS 工程和源文件、将源代码 链编程为可执行文件,并在设备运行代码,或者在 iPhone 模拟器上调试代码所需的各种工 具。总之,Xcode 将一系列功能整合在一起,让 iOS 应用程序开发变得更加容易,其提供的 具体功能如下。 ‰‰ 对软件产品进行定义的工程管理系统。 ‰‰ 代码编辑环境(包括为文法显示不同颜色、代码补全以及符号指示等多种功能)。 ‰‰ 高级文档阅读工具(用于阅读搜索苹果文档)。 ‰‰ 对上下文敏感的检查工具(用于查看选定代码符号的信息)。 ‰‰ 高级链编系统(具有依赖检查及链编规则计算功能)。 ‰‰ GCC 编译器(支持对 C、C++、Objective-C、Objective-C++、Objective-C 2.0 及其他 语言进行编译)。 ‰‰ 集成源码级的调试功能(此功能使用 GDB 来实现)。 ‰‰ 分布式计算(此功能可以将巨大的工程分布到数台联网的机器上运行)。 ‰‰ 预测编译(此功能可以加速单个文件的编译周转时间)。 ‰‰ 高级调试功能(例如停顿和继续运行,而且可以定制数据格式化方式)。 ‰‰ 高级重构工具(这些工具可以在不改变整体行为的前提下对代码进行全局性的修改)。 (续) 第 1 章 初识 iOS、Objective-C 和 Xcode   17 ‰‰ 工程快照的支持(工程快照是一种轻量级的本地源代码管理形式)。 ‰‰ 支持启动性能工具对软件进行分析。 ‰‰ 支持源代码管理集成。 ‰‰ 支持使用 AppleScript 实现链编过程自动化。 ‰‰ 可以生成 DWARF 和 Stabs 调试信息(所有的新工程都会默认生成 DWARF 调试信息)。 创建一个新的 iOS 应用程序,需先在 Xcode 中创建一个新的工程。所有和应用程序相 关的信息,包括源文件、链编设置以及将所有这些事物集成在一起的规则都由该工程管理。 Xcode 工程的中心部分是一个工程窗口,如图 1-7 所示。 图 1-7 Xcode 工程窗口 通过 Xcode 链编应用程序的时候,可将其链编至 iPhone 模拟器或设备。模拟器为应用 程序测试提供本地环境,可以通过它测试应用程序是否具有正确行为。当应用程序的基本行 为符合预期后,再通过 Xcode 将其链编到设备上,然后在已连接至计算机的 iOS 设备上运行 程序。在设备上运行应用程序是最终测试环境。在这一测试过程中,Xcode 允许将内建调试 器绑定至设备上运行的代码,直接在设备上进行调试,在 Xcode 中运行一个工程的流程如 图 1-8 所示。 2. Interface Builder Interface Builder 以“所见即所”得的方式组装用户界面。通过 Interface Builder,可以把 事先配置好的组件拖拽到应用程序窗口,最终组装出应用程序的用户界面(如图 1-9 所示)。 这里所说的组件既包括标准系统控件(例如切换控件、文本字段及按键),也包括一些定制 视图(用于表现应用程序特有的外观)。 18   第一部分 准 备 篇 图 1-8 在 Xcode 中运行一个工程的流程 图 1-9 使用 Interface Builder 创建 iOS 界面 将控件放在窗口表面后,还可以拽着它在四周移动,为其寻找合适的位置。同时,可以 使用 inspector 配置组件属性,并在对象和代码之间建立正确关联。在用户界面达到要求后, 可以将这些界面的内容保存到 NIB 文件(一种定制的资源文件格式)。 第 1 章 初识 iOS、Objective-C 和 Xcode   19 在 Interface Builder 中创建的 NIB 文件包含 UIKit 在运行时为应用程序重建对象所需的一 切信息。在加载 NIB 文件的时候,系统会为保存在文件的中每个对象创建一份运行时版本, 然后再对其进行配置,使之和 Interface Builder 中的状态保持一致。另外,系统还将根据制定 的关联信息为新建对象和应用程序已有对象建立关联。这些关联可以为代码提供指向 NIB 文 件包含的对象的指针,同时也为这些对象与代码中的用户动作进行通信提供必要信息。 总而言之,在创建应用程序用户界面的时候,使用 Interface Builder 可以节省大量的时 间。使用 Interface Builder 之后,在创建、配置及摆放界面对象的时候就无须编写定制代码, 因为它是一种可视化的编辑器,编辑时所见即运行时所得的界面。 注意 从 Xcode 4.0 起,Interface Builder 已经整合到 Xcode 中。 3. Instruments 为确保软件具有最佳的用户体验,在 iOS 应用程序运行于模拟器或设备上时,可以利用 Instruments 环境分析其性能。Instruments 会收集运行程序的数据,并以时间线方式展现数据。 可以采集的应用程序数据包括应用程序内存使用情况、磁盘活动、网络活动以及图形性能。 时间线视图可以同时显示不同类型的信息,这样,就可以把整个应用程序的行为相互关联起 来,而非仅看到某一特定方面的行为。如果还需要更加详细的信息,可以查看 Instruments 收 集的精细采样,如图 1-10 所示。 图 1-10 使用 Instruments 调整应用程序 除了时间线视图,Instruments 还提供一些工具用于对不同时间的应用程序行为进行分析。 举个例子,Instruments 窗口允许将多次运行的数据保存起来,这样就可以看到应用程序的行 为是否确实有所改善,或仍需调整。也可以把这些数据保存在一份 Instruments 文档中以备随 时查看。 20   第一部分 准 备 篇 4. Shark Shark 是自带的分析 iOS 应用程序性能的工具。当程序运行在 iOS 设备上时,可以通过 Shark 从几个方面对代码进行剖析。剖析结果可认为是应用程序运行时行为的统计采样,可 以通过 Shark 的数据采集和图表化工具对剖析结果进行分析。使用这些工具可以直观地了解 应用程序运行时的行为,进而找到潜在的产生问题之处。 1.2 认识 Objective-C Objective-C 是在 C 语言的基础上加入面向对象特性扩充而成的编程语言。在一定程度 上,可以把 Objective-C 看做 ANSI 版本 C 语言的一个超集,它支持相同的 C 语言基本语法, 同时扩展了标准 ANSI C 语言的语法,包括定义类、方法和属性。当然还有其他一些结构的 完善和拓展,例如类别(Category)的出现。 Objective-C 类的语法和设计都基于第一个面向对象的编程语言 Smalltalk 4.1。 1.2.1 发展历程及版本变化 1980 年年初,布莱德·确斯(Brad Cox)发明了 Objective-C,它是基于 SmallTalk-80 的 语言而发展起来的。Objective-C 是在 C 语言基础上添加扩展而创造出来的能够创建和操作对 象的一门新的程序设计语言。对 Objective-C 最主要的描述是布莱德·确斯于 1986 年出版的 《Object-oriented Programming,An Evolutionary Approach》。 1988 年,NeXT Software 公司开发了 Objective-C 语言库,以及 NEXTSTEP 开发环境。 1996 年年底,Apple 公司收购 NeXT Software 公司,使 NEXTSTEP/OPENSTEP 环境成 为苹果操作系统下主要发行版本 OS X 的基础。该版本的开发环境被 Apple 公司称为 Cocoa。 2006 年,Apple 发布了 Objective-C 2.0,该版本增加了现代的垃圾收集语法改进、运行 时性能改进和 64 位支持等功能。 2007 年 10 月,发布的 Mac OS X 10.5 中包含了 Objective-C 2.0 的编译器。 Objective-C 是 GCC 的一个前端,可以编译混合使用 C++ 与 Objective-C 语法的源文件。 Objective-C++ 是 C++ 的扩展,类似于 Objective-C 是 C 的扩展。下面列出了 Objective-C 与 C++ 的区别。 ‰‰ Objective-C 不支持多重继承(同 Java 和 Smalltalk), 而 C++ 语言支持多重继承。 ‰‰ C++ 类不能从 Objective-C 类继承,反之亦然。 ‰‰ Objective-C 是动态类型,所以它的类库比 C++ 容易操作。Objective-C 在运行时允 许根据字符串名字来访问方法和类,还可以动态连接和添加类。 ‰‰ C++ 属于面向对象编程里的 Simula 67(一种早期 OO 语言)学派,而 Objective-C 属于 Smalltalk 学派。Simula 67 学派更安全,因为大部分错误可以在编译时查出。而 Smalltalk 学派更灵活,比如一些看来无误的 Smalltalk 程序拿到 Simula 67 那里就无法通过。 ‰‰ 在 C++ 里,对象的静态类型决定是否可以发送消息给它,而对于 Objective-C 来说, 第 1 章 初识 iOS、Objective-C 和 Xcode   21 这一点由动态类型来决定。 ‰‰ Objective-C 定义内部不能定义 C++ 命名空间。 ‰‰ Objective-C 类的成员变量不能包括不含默认构造函数和 / 或含有虚方法的 C++ 类对 象,但使用 C++ 类指针无此限制(可以在 -init 方法中进行初始化)。 ‰‰ C++“传递值”的特性不能用在 Objective-C 对象上,而只能传递其指针。 ‰‰ Objective-C 声明不能存在于 C++ 模板声明中,反之亦然。但 Objective-C 类型可以用 在 C++ 模板的参数中。 ‰‰ Objective-C 和 C++ 的错误处理语句不同,各自的语句只能处理各自的错误。 ‰‰ Objective-C 错误使得 C++ 对象退出时,C++ 析构函数不会被调用。新的 64 位运行时 解决了这个问题。 1.2.2 语言性能与分析 Objective-C 是非常“实际”的语言 。它是一个用 C 语言写成的很小的运行库,使得应 用程序的大小增加很少,与此相比,大部分 OO 系统需要极大的运行时虚拟机来执行。用 Objective-C 写成的程序通常不会比其源代码和函式库(通常无须包含在软件发行版本中)大 太多,不会像 Smalltalk 系统,即使只是打开一个窗口也需要极大的内存。由于 Objective-C 的动态类型特征,Objective-C 不能对方法进行内联(inline)一类的优化,使得 Objective-C 的应用程序比 C 或 C++ 程序更小。 Objective-C 具有较强的编译器适应性 。Objective-C 可以在现存 C 编译器基础上实现, 而不需要编写一个全新的编译器。这个特性使得 Objective-C 能利用大量现存的 C 代码、库、 工具和编程思想等资源。现存 C 库可以用 Objective-C 包装器来提供一个 Objective-C 使用的 OO 风格界面包装,这些特性极大地降低了进入 Objective-C 的门槛。 Objective-C 垃圾回收机制的引进 。最初 Objective-C 不支持垃圾回收机制。Apple 发布 的 Xcode 4 已经支持自动释放功能。Xcode 4 中的自动释放(Automatic Reference Counting, ARC)机制,不需要用户手动释放(Release)一个对象,而是在编译期间,编译器会自动帮 助人们添加那些以前经常写的 [NSObject release]。Apple 公司在其 Mac OS X 10.5 中也提供 了这种实现。 Objective-C 新的命名规则应用。Objective-C 不包括命名空间机制,处理这个问题的方 法是程序设计师必须为其类别名称加上前缀。由于前缀往往较短(相比命名空间),这时常 引发冲突。2007 年开始,在 Cocoa 编程环境中,所有 Mac OS X 类别和函式均有“NS”前缀 (例如 NSObject 或 NSButton)以清楚地标识它们属于 Mac OS X 核心。使用“NS”是由于这 些类别的名称是在 NeXTSTEP 开发时定下的。 Objective-C 独有个性。虽然 Objective-C 是 C 的严格超集,但它没有将 C 的基本类型 视为第一级的对象。和 C++ 不同,Objective-C 不支持运算子多载(不支持 ad-hoc 多型)。 与 C++ 不同(但和 Java 相同),Objective-C 只允许对象继承一个类别(不设多重继承)。 Categories 和 Protocols 可以提供很多多重继承的好处,而且没有什么缺点,例如额外执行时 22   第一部分 准 备 篇 间过重和二进制不兼容。 由于 Objective-C 使用动态运行时类型,而且所有的方法都是函数调用(有时甚至系统 调用也是如此),很多常见的编译时性能优化方法都不能应用于 Objective-C(例如内联函 数、常数传播、交互式优化、纯量取代与聚集等)。这使得 Objective-C 性能劣于类似的对 象抽象语言(如 C++)。不过 Objective-C 拥护者认为,既然 Objective-C 运行时消耗较大, Objective-C 本来就不应该应用于 C++ 或 Java 常见的底层抽象。 1.2.3 框架和代码的关系 框架不仅仅是将提供各自服务的各个类混杂在一起的杂物袋。这些面向对象的框架是类 的集合,每个类构建一个问题空间,并提供完整的解决方案。框架提供的不是根据需要可以 使用的离散服务(如函数库),而是制定并实现整个应用程序的结构,因此编写的代码必须适 应该结构。因为此应用程序结构是通用的,可以使其特殊化,以满足特定应用的要求。与其 说是设计一个程序并插入资源库函数,不如说是将应用程序代码插入到框架提供的设计中。 要使用框架,必须接受它定义的应用程序结构,然后根据需要,尽可能多地使用和定制它 的类,将特定的应用进行改造,以适合该结构。框架中的类相互依赖构成一个整体。乍一看, 在应用程序中要求代码适应框架的结构,似乎有限制性。事实刚好相反。框架提供了很多方式 修改和扩展其通用行为。它只要求接受这样的观点,所有应用都以同样的基本方式来运行,因 为这些应用全部基于同样的结构。打个比方,Objective-C 框架就像房屋的框架,而应用程序代 码就好比大门、窗户、壁板和其他元素,是这些东西使房子变得与众不同,如图 1-11 所示。 䇇䇤⧭㿓 ⫛㕌 䇇䇤⧭㿓 ⫛㕌 䇇䇤⧭㿓 ⫛㕌 䇇䇤⧭㿓 ⫛㕌 ㌓ソ ⫛㕌 图 1-11 Objective-C 框架和应用程序代码 第 1 章 初识 iOS、Objective-C 和 Xcode   23 从使用框架和将代码与框架整合的观点看,有两种类。 ‰‰ 现成的。一些类定义了现成对象,也就是可以随时使用的对象。只需要创建类的实 例,然后根据需要使用这些实例。 ‰‰ 通用的(或泛型)。对于泛型框架类,可以(在有些情况下必须)创建它们的子类, 并覆盖某些方法的实现。通过对它们进行子类化,将代码引入到应用程序结构中。框 架会在适当的时刻,调用子类的方法。 将泛型框架类子类化,是将程序特定的代码整合到框架所提供的结构中的主要技巧。但 这并不是唯一技巧,而且在很多情况下,也不是首选的技巧。 1.3 基于非苹果机平台搭建开发环境 Xcode 主要运行在苹果操作系统上。如果自己有苹果机就很简单,可以直接在上面搭建 Objective-C 的应用开发环境。但实际情况是当前很多人手中没有苹果机,或者自己的机器没 有办法直接安装苹果操作系统。为了学习 Objective-C 语言,而去买一台价格不菲的苹果机, 对于很多人,特别是正在读书的学生,确实是一笔很大的开销。 对于很多人,基本上自己手中都有一台装有 Windows 操作系统的机器,能不能基于 Windows 操作系统机器来搭建 Objective-C 应用开发环境 ? 如果你的机器支持支持虚拟技术 (VT),基本上都可以。本节主要围绕这个问题展开介绍。 注意 很多人想感受苹果机的性能,但是又不肯购买正版的苹果机,于是就产生了“黑苹 果”。所谓的“黑苹果”就是把机器组成了能够直接安装苹果操作系统的机器,与之对应, 把正版的苹果机称为“白苹果”。但是“黑苹果”有一个很难解决的问题——驱动问题, 一直没有很好的解决方案。在“黑苹果”上也能安装 Xcode,据说可以实行正式发布。 1.3.1 前期准备 在安装Mac OS X 之前,需要检验自己的机器是否支持虚拟技术,同时需要下载 VMWare、Mac OS X、SDK 安装包、支持 Mac OS 安装的特性启动引导包。 1. 支持虚拟技术 运行 Mac OS X,硬件上需要电脑支持虚拟技术(VT)。可以使用 SecurAble 软件检测机 器是否支持虚拟技术。有的机器没有启动 VT,这就需要在 BIOS 中开启。 开启 VT 需几个方面的条件支持 :芯片组自身支持、BIOS 提供支持、处理器自身支持 和操作系统支持。操作系统方面,主流操作系统均支持 VMM 管理,因此无须考虑。而芯片 组方面,从 Intel 945(除上网本外)时代开始均已支持,因此也无须考虑。 2. 下载 VMWare VMWare-workstation 版本不断升级,新的版本功能越来越强大,在使用上也越来越方 24   第一部分 准 备 篇 便,建议下载最新的操作版本。笔者所使用的 VMWare-workstation 版本是 9.0.1。最好再下 载一个支持 Mac OS X 的特性启动引导包,这样可以大大降低安装的复杂性。 3. 安装 Mac OS X 这里使用的是苹果操作系统 Mac OS X 10.7.4。目前苹果开发者中心提供的 Xcode 最新 版本是基于 iOS 7.0 版本的。安装 Xcode 5.0 需要安装 Mac OS X 10.8,这对硬件的要求相对 高些,但目前使用第三代英特尔酷睿 i3、i5 或者 i7 的机器基本上都支持 Xcode 5.0 版本。至 于安装什么版本的 Mac OS X,可以根据自己的机器配置情况而灵活选择。 4. 下载 SDK 安装包 这里使用的 SDK 安装包是 Xcode 4.5.dmg,可以到苹果开发者中心下载,网址为 https:// developer.apple.com/devcenter/ios/index.action。 至于下载什么样的 SDK,需要考虑自己的机器能装什么版本 Mac OS,对于苹果雪豹操 作系列,最好下载 SDK 版本不要超过 4.0 版本。如果机器配置比较好,安装苹果美洲虎能升 级到 10.7.4. 或者以上,建议下载 SDK 4.5 版本或者更新的版本。 注意 在苹果官网下载 iPhone SDK 开发包时,需要用户注册才有权限下载。 5. 支持 Mac OS X 安装的特性启动引导包 网络上有很多种支持 Mac OS X 的安装特性启动引导包,例如 Unlock-All-Vmware 和 Macosx_Guest_Vmware 等,要根据需求选择。 如果不安装支持 Mac OS X 的特性启动引导包,通过 Darwin.iso 或 Rebel EFI.iso 用于 引导光盘的 ISO 镜像文件,也可以成功安装,但是必须在安装成功后,把“CD/DVD”的 “Ues Floppy Image File”设置为该引导文件。 1.3.2 创建用于安装 Mac OS X 的 VMWare 虚拟机 安装 VMWare-workstation 是一件很简单的事情,只要按照提示进行下一步就可以。安 装 VMware Workstation 并完成重启后,就可以安装支持 Mac OS X 的特性启动引导包了。解 压安装包后,双击 install.cmd 文件就可以。安装成功后,将会在“Guest Operating System” 项中增添“Apple Mac OS X”选项。 注意 如果你的操作系统是 Windows 7 或者 Windows 8, 安装 install.cmd 文件的时候,必须以 管理员模式启用它。 现在 VMWare-workstation 已经安装成功了,可以创建用于 Mac OS X 的 VMWare 虚拟机 了。具体的创建步骤如下。 步骤 1 选择虚拟机的配置类型。如图 1-12 所示,推荐选择“Custom”项。 步骤 2 虚拟机硬件兼容性,默认设置。 第 1 章 初识 iOS、Objective-C 和 Xcode   25 步骤 3 虚拟机安装模式,默认设置。 步骤 4 选择虚拟机操作系统。 如图 1-13 所示,选择“Apple Mac OS X”选项,并选择版本“Mac OS X 10.7 64-bit”。   图 1-12 选择虚拟机的配置类型    图 1-13 选择操作系统    步骤 5 虚拟机命名。可以采用默认名字,也可以自定义名称。对于虚拟机安装的目录, 可以选用默认目录,也可以选择其他的安装目录。 步骤 6 处理器的配置。 可以使用默认配置,但是一般情况下是根据实际情况进行配置。例如,笔者的机器是双 核 CPU,所以将处理器配置为 1,每个处理器的内核为 2,如图 1-14 所示。 图 1-14 处理器的配置 26   第一部分 准 备 篇 步骤 7 配置虚拟机内存的大小。建议不低于 4GB。 步骤 8 配置虚拟机的网络。建议采取默认的配置,即“NAT”。 步骤 9 设置 I/O 类型。建议采取默认的配置,即“LSI Logic”。 步骤 10 选择盘。建议采取默认的配置,即“Create a new virtual disk”。 步骤 11 选择盘的类型。建议采取默认的配置,即“SCSI”。 步骤 12 设置盘的容量。建议不低于 40GB,同时在附属项中选择“Store virtual disk as a single file”。 步骤 13 删除软驱配置。在“Hardware”中选择“Floppy”,然后单击“Remove”按 钮,否则会引起系统无限重启。如图 1-15 所示。 图 1-15 删除软驱配置 步骤 14 配置显卡情况。在“Hardware”中选择“Display”, 在该选项中启用“Accelerate 3D graphics”( 3D 图形加速)。 步骤 15 配置 CD/DVD 格式类型。在“Hardware”中选择 “CD/DVD”,然后选择“Advanced”,把光驱设置为 SCSI 高级模 式,如图 1-16 所示。选择“Use ISO image file”,作为 Mac OS 镜 像文件所在的目录。 1.3.3 安装 Mac OS X 上一节我们创建了用于安装 Mac OS X 的虚拟机,本节介绍 图 1-16 光驱的配置 第 1 章 初识 iOS、Objective-C 和 Xcode   27 如何在虚拟机上安装 Mac OS X。 安装 Mac OS X 其实很简单,跟安装 Windows 操作系统差不多,下面分步骤介绍,注意 本节安装的 Mac OS X 操作系统版本为 10.7.4。 步骤 1 启动创建的虚拟机。如图 1-17 所示,单击“Mac OS X 10.7.4”,右键单击,在 显示的菜单项中选择“Power”,然后在其子菜单项中选择“Power On”,将会启动上一节中所 创建的虚拟机。 图 1-17 启动虚拟机 步骤 2 Mac OS X 安装启动。安装启动画面显示的时间会稍微长一些,大约 2 分钟。如 图 1-18 所示。 图 1-18 Mac OS X 安装启动 28   第一部分 准 备 篇 步骤 3 Mac OS X 语言选择。一般选择简体中文。 步骤 4 Mac OS X 实用工具。 这个步骤将完成对硬盘的设置,在该界面中单击“磁盘工具”项,如图 1-19 所示。 图 1-19 Mac OS X 实用工具 步骤 5 设置硬盘。在图 1-20 所示界面中单击硬盘图标,然后对硬盘进行格式化。 图 1-20 设置硬盘 第 1 章 初识 iOS、Objective-C 和 Xcode   29 格式采取默认的“Mac OS 扩展(日志式)”,输入硬盘的名称,例如输入“yingpan”作 为硬盘的名称,然后单击“抹掉”,就开始对硬盘进行“格式化”了。格式化完毕,关闭该 界面,将会显示步骤 4 中图 1-19 所示的界面,在该界面中单击“重新安装 Mac OS X”项。 步骤 6 Mac OS X 安装的启动。在图 1-21 界面中单击“继续”。 图 1-21 Mac OS X 安装的启动 步骤 7 Mac OS X 正式安装。 单击图 1-22 中的硬盘图标,然后单击“安装”,将正式进入 Mac OS X 安装。 图 1-22 Mac OS X 正式安装 30   第一部分 准 备 篇 步骤 8 Mac OS X 安装过程要进行多长时间取决于机器的配置,一般需要十几分钟。 步骤 9 创建账户,即设置你的用户名和密码。 步骤 10 配置自己的键盘模式。一般选择美式的方式。 步骤 11 安装完毕,在图 1-23 所示界面中单击“开始使用 Mac”,将会进入登录界面。 图 1-23 Mac OS X 安装完毕 1.3.4 安装 Xcode SDK 上一节已经成功安装了 Mac OS X,本节将安装 Xcode SDK。在安装 Xcode SDK 之前, 不得不考虑如何解决 Windows 和 Mac OS X 数据交互问题。首先介绍如何设置共享目录的 问题。 在 Windows 中下载完 Xcode SDK 等安装必备文件之后,可以将下载的安装包等文件刻 录成光盘 ;若不想把下载的安装包等文件刻录成光盘,也可以直接将这些文件传到虚拟机 VMWare 中来进行安装。 这里采用 Windows 和 VMWare 共享目录的方法实现。在 Windows 中需要将 Xcode SDK 文件夹共享给 VMWare 中的 Mac OS X。设置成文件共享的步骤如下。 步骤 1 在 Windows 中将目录 SDK 设置为共享。单击鼠标右键,选择属性→共享,选 中“在网络上共享这个文件夹”,单击确定按钮。 步骤 2 记下 Windows 的 IP 地址。 步骤 3 通过 Mac OS X 访问 Windows 的共享目录,以达到访问 Windows 的共享文件夹中 的 Xcode SDK 安装包。方法是:在 Mac OS X 中,单击主菜单 Finder 菜单项,按住 Cmd+K 键。 注意 在普通 PC 或笔记本上(非 Mac 电脑)使用 Cmd + K 快捷键连接服务器,在“smb://” 后输入 Windows 的 IP 地址“192.168.6.222”,单击“连接”按钮即可看到提示“选择需要共 享的目录 SDK”,单击“好”按钮确认,即可打开该共享目录。 第 1 章 初识 iOS、Objective-C 和 Xcode   31 步骤 4 在 Mac OS X 中可以看到 Windows 共享文件夹,在共享文件夹中找到 SDK 的 安装文件“Xcode 4.5.dmg”,用鼠标双击该文件图标,会看到一个弹出界面。 单击界面中展示的图标,会弹出如图 1-24 所示的界面。单击“Install”按钮,将会进入 安装认证。 图 1-24 Xcode SDK 安装起始界面 在图 1-25 所示的安装认证界面中,输入上一节中设置的 Mac OS X 用户名和密码,然后 单击“好”。 图 1-25 安装认证 32   第一部分 准 备 篇 安装进行中的界面如图 1-26 所示。 图 1-26 正在安装 安装成功的界面如图 1-27 所示。 图 1-27 安装成功 单击“Start Using Xcode”将会进入 Xcode 界面,如图 1-28 所示。 至此,基于非苹果机平台搭建的开发环境就完成了,现在就可以进行编程开发了。 第 1 章 初识 iOS、Objective-C 和 Xcode   33 图 1-28 Xcode 启动界面 1.4 小结 通过本章的介绍,我们了解了 iOS、Objective-C 和 Xcode 三者之间的关系,重点介绍了 iOS 和开发环境的搭建。iOS 包含了丰富的框架,是应用程序开发的基础,后面的章节将会 围绕这些框架由易到难逐步展开介绍,掌握这些框架的功能,就可以基于这些框架开发出多 彩强大的应用了。 第 2 章 创建你的第一个 iOS 应用程序 开发优秀的 iPhone/iPad 应用程序,需要大量的学习和实践。不过,有了 Xcode 工具和 iOS SDK,开发一个简单可用的程序并非难事。结合第 1 章搭建的开发环境,本章就可以创 建你的首个 iOS 应用程序。 在创建应用程序的过程中,可能会遇到很多不明白的问题,不要担忧,只需要根据本章 的介绍“照葫芦画瓢”,不理解的地方可以跳过,继续往下进行。本章的目的是,使你对基 础开发过程有初步了解。 2.1 应用程序的实现目标 本章要实现的应用程序主要有三个用户界面元素(如图 2-1 所示)。 ‰‰ 文本栏(用户输入信息); ‰‰ 标签(显示信息); ‰‰ 按钮(让应用程序在标签中显示信息)。 标签 按钮 文本栏 图 2-1 应用程序运行效果 第 2 章 创建你的第一个 iOS 应用程序   35 运行应用程序,单击文本栏会调出系统提供的键盘。使用键盘输入姓名后,单击 “Done”键将键盘隐藏,然后单击“Hello”按钮,即可在文本栏和按钮之间的标签中看到字 符串“Hello,你的姓名 !”。 注意 尽管本节显示的是 iPhone 用户界面,但使用的工具和技巧与开发 iPad 应用程序所用 到的完全相同;所以即使打算只开发 iPad 程序,仍可以将本章当作入门指南。 2.2 入门的开始 要创建本章的 iOS 应用程序,需要安装 Xcode 5.0 或更高版本。Xcode 是 Apple 的集成 开发环境(又称 IDE),用于 iOS 和 Mac OS X 的开发。在 Mac 上安装 Xcode 的同时,也会 安装 iOS SDK,它包含 iOS 平台的编程接口。 2.2.1 新建一个 Xcode 项目 新建一个 Xcode 项目主要步骤如下。 步骤 1 打开 Xcode(默认位置在“/ 应用程序”)。 如果从未在 Xcode 中创建或打开过项目,会看到一个“Welcome to Xcode”窗口,如 图 2-2 所示。 图 2-2 “ Welcome to Xcode”窗口 如果曾在 Xcode 中创建或打开过项目,会看到一个项目窗口,而不是“Welcome to Xcode”窗口。 36   第一部分 准 备 篇 步骤 2 新建应用程序模板窗口。 在“Welcome to Xcode”窗口中,单击“Create a new Xcode project”,或选取“File”→ “New”→“New project”。 Xcode 将打开一个新窗口并显示对话框,如图 2-3 所示,从中选 取一个模板。 图 2-3 新建应用程序模板对话框 Xcode 中内建了一些应用程序模板,可以使用这些模板开发常见类型的 iOS 应用程序。 例如“Tabbed”模板可以创建类似 iTunes 的应用程序,“Master-Detail”模板可以创建类似邮 件的应用程序。 步骤 3 新项目选项填写窗口。 在图 2-3 对话框左边的 iOS 部分选择“Application”,选择“Single View Application”, 然后单击“Next”。一个新对话框会出现,如图 2-4 所示,提示为应用程序命名,并为项目选 取附加选项。 步骤 4 填写“Product Name”、“ Company Identifier”和“Class Prefix”等栏位。 这里可以使用以下值。 ‰‰ Product Name:HelloWorld ‰‰ Company Identifier:公司标识符(如果没有,可以使用 edu.self) ‰‰ Class Prefix:HelloWorld 注意 Xcode 使用输入的产品名称来命名项目和应用程序,使用类前缀名称来命名创建的类。 例如,Xcode 会自动创建一个应用程序委托类,命名为 HelloWorldAppDelegate。如果输入不 同的值作为类前缀,则应用程序委托类将命名为“你的类前缀名称 AppDelegate”。 第 2 章 创建你的第一个 iOS 应用程序   37 图 2-4 新项目选项填写对话框 步骤 5 在“Device”弹出式菜单中选取“iPhone”。 步骤 6 选取“Use Storyboard”和“Use Automatic Reference Counting”选项,但不选 定“Include Unit Tests”选项。 注意 在 Xcode 5 之前,此项需要对二者进行选择,在 Xcode 5 之后,系统默认都是“ Use Storyboard”。 步骤 7 单击“Next”。此时出现另一个对话框,用来指定项目存储的位置。 不选定“Source Control”选项,然后单击“Create”。 Xcode 在工作区窗口中打开新项目。 从结构布局上,Xcode 的工作区窗口可以按如图 2-5 所示布局进行分类,不同区域担负 着不同职责功能。在接下来的介绍中,你将会用到窗口中标识的按钮和区域。 如果工作区窗口中的实用工具区域已打开(如图 2-5 窗口中所示),可以暂时把它关闭, 因为稍后才会用到它。最右边的“View”按钮可控制实用工具区域。当实用工具区域可见 时,该按钮为 。如有需要,单击“View”按钮关闭实用工具区域。 2.2.2 在模拟器中查看应用程序的效果 即使不编写任何代码,也可以构建应用程序,并在模拟器(已包含在 Xcode 中)中运 行。模拟器可模拟应用程序在 iPhone 设备上运行,让你初步了解应用程序的外观和行为。 按照以下步骤就可以在模拟器中运行你的应用程序。 步骤 1 在 Xcode 工具栏的“Scheme”弹出式菜单中选定“HelloWorld”→“iPhone 7.0 Simulator”选项。如果弹出式菜单中该选项未被选定,可以把它打开,然后从菜单中选取 “iPhone 7.0 Simulator”,如图 2-6 所示。 iPhone 38   第一部分 准 备 篇 ⹅㉀㎙ 䊬㾱➕㝆 Ⳟ➙⭐⨗㬞⤬⭆ ⢁ゎ㡘➕㝆 㬴㵝➕㝆 㬖䇤⹅㉀㤙䈓⢁ゎ㡘㤙䈓⭝⼞㡘㤙䈓 图 2-5 工作区窗口的划分 图 2-6 设置运行模拟设备 第 2 章 创建你的第一个 iOS 应用程序   39 步骤 2 单击 Xcode 工具栏中的“Run”按钮,或选取“Product”→“Run”。 在 Xcode 生成项目后,模拟器应该会自动启动。因为指定的是 iPhone 产品而非 iPad 产 品,模拟器会显示一个类似 iPhone 的窗口。在模拟的 iPhone 屏幕上,用模拟器打开你的应 用程序,外观如图 2-7 所示。 图 2-7 程序运行效果 此刻,你的应用程序只显示一个空白的画面。要了解空白画面是如何生成的,需要了解 代码中的对象,以及它们如何紧密协作来启动应用程序。 现在,退出模拟器(选取“iOS Simulator”→“Quit iOS Simulator”,注意不是退出 Xcode)。 2.3 启动一个应用程序 由于项目是基于 Xcode 模板开发的,在运行应用程序时,大部分基本的应用程序环境已 经自动建立。例如,Xcode 创建一个应用程序对象(以及其他一些东西)来建立运行循环(运 行循环将输入源寄存,并将输入事件传递给应用程序)。该工作大部分是由 UIApplicationMain 函数完成的,此函数由 UIKit 框架提供,在项目的 main.m 源文件中自动调用。 注意 UIKit 框架提供应用程序构建和管理其用户界面所需的全部类。 UIKit 框架只是 Cocoa Touch 提供的面向对象的众多框架中的一个,而 Cocoa Touch 是所有 iOS 应用程序的 应用环境。 40   第一部分 准 备 篇 2.3.1 探究 main.m 源文件 确定项目导航器已在导航器区域中打开,项目导航器显示项目中的所有文件。如果 项目导航器未打开,单击导航器选择栏最左边的按钮,如图 2-8 所示。 导航器选择栏 图 2-8 导航器选择栏 单击项目导航器中 Supporting Files 文件夹旁边的展示三角形打开文件夹,选择 main.m, 在 Xcode 工作区窗口的主编辑器区域打开源文件,如图 2-9 所示。 图 2-9 main.m 文件 main 函数调用自动释放池(autorelease pool)中的 UIApplicationMain 函数。 @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([HelloWorldAppDelegate class])); } 第 2 章 创建你的第一个 iOS 应用程序   41 @autoreleasepool 语句支持“自动引用计数”(ARC)系统。ARC 可自动管理应用程序 的对象生命周期,确保对象在需要时一直存在,直到不再需要。 调用 UIApplicationMain 会创建一个 UIApplication 类的实例和一个应用程序委托的实例 (在本节中,应用程序委托是 HelloWorldAppDelegate,由 Single View 模板提供)。应用程序 委托的主要作用是提供呈现应用程序内容的窗口,在应用程序呈现之前,应用程序委托也执 行一些配置任务。 注意 委托是一种设计模式,在此模式中,一个对象代表另一个对象,或与另一个对象协调 工作。 2.3.2 分析属性列表文件 在 iOS 应用程序中,窗口对象为应用程序的可见内容提供容器,协助将事件传递到 应用程序对象,协助应用程序对设备的摆放方向做出响应。窗口本身是不可见的。调用 UIApplicationMain 也会扫描应用程序的 Info.plist 文件。Info.plist 文件为信息属性列表,即键 和值配对的结构化列表,它包含应用程序的信息,例如名称和图标。 在项目导航器的 Supporting Files 文件夹中,选择 HelloWorld-Info.plist。Xcode 在窗口的 编辑器区域打开 Info.plist 文件。 在本节中,你不需要查看 Supporting Files 文件夹中的文件,因此可以在项目导航器中关 闭此文件夹以避免分散注意力。同样,单击 Supporting Files 文件夹图标旁边的展示三角形以 关闭该文件夹。 因为你已选取在项目中使用串联图,所以 Info.plist 文件还包含应用程序对象应该载 入的串联图的名称。串联图包含对象、转换以及连接的归档,它们定义了应用程序的用户 界面。 在 HelloWorld 应用程序中,将串联图文件命名为MainStoryboard.storyboard(注意, Info.plist 文件只显示名称的第一部分)。在应用程序启动时,载入 MainStoryboard.storyboard, 接着根据它对初始视图控制器进行实例化。 注意 视图控制器是管理区域内容的对象,而初始视图控制器是应用程序启动时载入的第一 个视图控制器。 2.3.3 查看串联图 视图是一个对象,它在屏幕的矩形区域中绘制内容,并处理由用户触摸屏幕所引起的 事件。一个视图也可以包含其他视图,这些视图称为分视图。一个视图在添加了一个分视图 后,就称为父视图,这个分视图称为子视图。父视图、子视图以及子视图的子视图(如有) 形成一个视图层次。一个视图控制器只管理一个视图层次。 42   第一部分 准 备 篇 注意 “模型 - 视图 - 控制器”( Model-View-Controller,MVC)设计模式定义了应用程序对 象的三种角色,HelloWorld 应用程序中的视图和视图控制器体现了其中的两种,而第三种为 模型对象。在 MVC 中,模型对象表示数据(例如日历应用程序中的待办事项或绘图程序中 的图形),视图对象知道如何显示模型对象所表示的数据,控制器对象充当模型和视图的媒 介。在 HelloWorld 应用程序中,模型对象为字符串,用来保存用户输入的名称。现在不需 要了解更多有关 MVC 的信息,但最好开始思考应用程序中的对象如何扮演不同的角色。 在接下来的步骤中,要给由 HelloWorldViewController 管理的视图添加三个分视图,以 创建视图层次 ;这三个子视图分别表示文本栏、标签和按钮。可以在串联图中看到视图控制 器及其视图的模样。 在项目导航器中选择 MainStoryboard.storyboard。Xcode 在编辑器区域打开串联图。串 联图对象后面的区域看起来像图纸,该区域称为画布。打开默认串联图后,工作区窗口如 图 2-10。串联图包括场景和过渡。场景代表视图控制器,过渡则表示两个场景之间的转换。 画布 场景 场景台 大纲视图 初始场景指示器 图 2-10 串联图 第 2 章 创建你的第一个 iOS 应用程序   43 因为 Single View 模板提供一个视图控制器,应用程序中的串联图只包含一个场景,所 以没有过渡。画布上指向场景左侧的箭头是“初始场景指示器”(initial scene indicator), 它 标识应用程序启动时应该首先载入的场景(通常初始的场景就是初始视图控制器)。 在画布上看到的场景为“Hello World View Controller”,它是由 HelloWorldViewController 对象来管理的。Hello World View Controller 场景由一些项目组成,显示在 Xcode 大纲视图(在 画布和项目导航器之间的面板)。视图控制器由以下项目组成。 ‰‰ 第一响应器占位符对象。“First Responder”是一个动态占位符,在应用程序运行时, 它应该是第一个接收各种事件的对象。这些事件包括以编辑为主的事件(例如轻按文 本栏以调出键盘)、运动事件(例如摇晃设备)和操作消息(例如当用户轻触按钮时 该按钮发出的消息)等。本节不会涉及第一响应器的任何操作。 ‰‰ Exit 占位符对象,用于展开序列。默认情况下,当用户使子场景消失时,该场景的视 图控制器展开(或返回)父场景,即转换为该子场景的原来场景。不过,Exit 对象使 视图控制器能够展开任意一个场景。 ‰‰ HelloWorldViewController 对象。串联图载入一个场景时,会创建一个视图控制器类 的实例来管理该场景。 ‰‰ 一个视图。列在视图控制器下方(要在大纲视图中显示此视图,可能要打开“Hello World View Controller”旁边的展示三角形)。此视图的白色背景就是在 Simulator 中 运行该应用程序时所看到的背景。 注意 应用程序的窗口对象在串联图中并未表示出来。 在画布中,场景下方的区域称为场景台。图 2-10 中,场景台显示了视图控制器的名称, 即“Hello World View Controller”。其他时候,场景台可包含代表第一响应器、Exit 占位符对 象和视图控制器对象的图标。 2.4 检查视图控制器及其视图 一个视图控制器负责管理一个场景,而一个场景代表一个内容区域。在该区域 中看到的内容,是在视图控制器的视图中定义的。在本节中,可以更仔细地查看由 HelloWorldViewController 所管理的场景,并学习如何调整视图的背景颜色。 2.4.1 如何使用检查器 应用程序启动时,载入主串联图文件,然后实例化初始视图控制器。初始视图控制器管 理用户打开应用程序时看到的第一个场景。因为 Single View 模板只提供一个视图控制器,该 视图控制器自动设定为初始视图控制器。可以使用 Xcode 检查器来验证视图控制器的状态, 并查看关于它的其他信息。 打开检查器的步骤如下。 44   第一部分 准 备 篇 步骤 1 如有需要,单击项目导航器中的 MainStoryboard.storyboard,在画布上显示场景。 步骤 2 在大纲视图中,选择“Hello World View Controller”,列在“First Responder” 下方。工作区窗口外观如图 2-11 所示。 图 2-11 检查器工作场景 注意 场景和场景台都有蓝色外框,并且已选定场景台中的视图控制器对象。 步骤 3 单击工具栏最右边的“View”按钮,在窗口右边显示实用工具区域(或者选取 “View”→“Utilities”→“Show Utilities”)。 步骤 4 在实用工具区域中单击“Attributes”按钮,打开 Attributes 检查器。 “Attributes”按钮位于实用工具区域顶部的检查器选择栏中,从左边数第四个按钮,如 图 2-12 所示。在Attributes 检查器的“View Controller”部分,可以看到已选定“Initial Scene”选项。 第 2 章 创建你的第一个 iOS 应用程序   45 注意 确定“Initial Scene”选项一直是选定的状态。如果取消选定这个选项,初始场景指示 器会从画布中消失。 检查器选择栏 图 2-12 检查器选择栏 2.4.2 更改视图的背景颜色 视图在 Simulator 中运行应用程序时提供的是白色背景。要确定应用程序工作正常,可 以将视图的背景设定为白色以外的其他颜色,并再次在 Simulator 中运行应用程序来验证新 颜色的显示。在更改视图的背景之前,要确定串联图仍打开在画布上。 注意 如有需要,单击项目导航器中的 MainStoryboard.storyboard,在画布上打开串联图。 1. 设定视图的背景颜色 设定视图控制器的视图背景颜色的步骤如下。 步骤 1 在大纲视图中,单击“Hello World View Controller”旁边的展示三角形(如果 还未打开),然后选择“View”。 Xcode 在画布上高亮显示视图区域。 步骤 2 在实用工具区域顶部的检查器选择栏中单击“Attributes”按钮,打开 Attributes 检查器(如果还未打开)。 步骤 3 在 Attributes 检查器中,单击 Background 弹出式菜单(如图 2-13 所示)中的 46   第一部分 准 备 篇 白色矩形,打开 Colors 窗口,矩形显示该项目当前的背景颜色。 注意 并非单击白色矩形,而是单击“Default”打开弹出式菜单,从中选取“Other”。 步骤 4 在 Colors 窗口中,选择白色以外的颜色。注意,在选择视图时 Xcode 高亮显示 该视图,所以画布上的颜色可能和 Colors 窗口中的颜色看起来不同。 步骤 5 关闭 Colors 窗口。 步骤6  单 击“Run”按钮或选取“Product” →“Run”, 在 Simulator 中测试你的 应用程序,效果如图2-14 所示。确定Xcode 工具栏中的Scheme 弹出式菜单仍然显示 “HelloWorld”→“iPhone 7.0 Simulator”。             图 2-13 Background 菜单        图 2-14 背景颜色设置为红色的效果 注意 在运行应用程序前,可以不必存储你的工作,因为单击“Run”或选取“Product”→“Run” 时,Xcode 会自动存储改过的文件。 2. 恢复视图的背景颜色 恢复视图的背景颜色的步骤如下。 步骤 1 在 Attributes 检查器中,单击箭头打开 Background 弹出式菜单。 注意 Background 弹出式菜单中的矩形已改变成显示在 Colors 窗口中所选取的颜色。如果单 击的是有颜色的矩形而不是箭头,Colors 窗口会重新打开。要想重新使用视图原来的背景颜 色,从 Background 菜单中选取该颜色即可,比从 Colors 窗口中找要简单得多。 第 2 章 创建你的第一个 iOS 应用程序   47 步骤 2 在 Background 弹出式菜单中选取“Recently Used Colors”中列出的白色方块。 步骤 3 单击“Run”按钮编译和运行应用程序,并存储所做的更改。 步骤 4 验证了应用程序重新显示白色背景后,退出 Simulator。 在运行应用程序时,Xcode 可能会在工作区窗口的底部打开调试区。由于本节不会用到 该面板,可以将其关闭,以腾出更多空间。 2.5 对视图进行配置和管理 Xcode 提供了对象库,可以将库中的对象添加到串联图文件。其中,一些对象属于视 图中的用户界面元素,例如按钮和文本栏 ;其他对象为高级对象,例如视图控制器和手势 识别器。 Hello World View Controller 场景已经包含一个视图,现在需要添加一个按钮、一个 标签和一个文本栏。然后在这些元素和视图控制器类之间建立连接,使元素提供你想要的 行为。 2.5.1 新增用户界面元素 将对象库中的用户界面(UI)元素拖移到画布的视图中,以便添加用户界面元素。将 UI 元素添加到视图后,可以适度移动它们的位置和调整大小。 1. 将 UI 元素添加到视图并适当进行布局 将元素添加到视图的步骤如下。 步骤1 如有需要,选择项目导航器中的 MainStoryboard.storyboard,在画布上显示 Hello World View Controller 场景。 步骤 2 如有需要,打开对象库。 对象库出现在实用工具区域的底部。如果看不到对象 库,可以单击其按钮,如图 2-15 所示。 步骤 3 在对象库中,从“Objects”弹出式菜单中选 取“Controls”。 Xcode 将控制列表显示在弹出式菜单下方。该列表显 示每个控制的名称、外观及其功能的简短讲述。 步骤 4 添加视图对象。 从列表中拖拽一个文本栏、一个圆角矩形按钮和一个 标签到视图上,一次添加一个。 步骤 5 调整文本栏位置。 在视图中,拖移文本栏将其放置在视图的左上角附 对象库 图 2-15 对象库按钮 48   第一部分 准 备 篇 近。在移动文本栏或任何其他 UI 元素时,会出现蓝色的虚线(称为对齐参考线),有助于将 项目与视图的中心和边缘对齐。当看到视图的左方和上方对齐参考线(如图 2-16 所示)时, 停止拖移文本栏。 图 2-16 添加视图对象 步骤 6 在视图中调整文本栏的大小。 通过拖移控制柄(显示在元素边框上的小白 方块)调整 UI 元素的大小。一般情况下,在画布 上或在大纲视图中选择一个元素,该元素的调整 大小控制柄就会显露出来。在本例中,因为刚刚 停止拖移,文本栏应该已被选定。如果文本栏外 观如图 2-17 所示,就可以调整它的大小 ;否则要 在画布上或在大纲视图中选择它。 拖移文本栏右侧的控制柄,直到视图最右侧的对齐参考线出现。当画布如图 2-18 所示 图 2-17 调整文本栏大小 第 2 章 创建你的第一个 iOS 应用程序   49 时,停止调整文本栏大小。 步骤 7 在选定文本栏时,打开 Attributes 检查器(如有需要)。 步骤 8 在 Text Field Attributes 检查器顶部附 近的 Placeholder 栏中输入 Your Name。 顾名思义,Placeholder 栏提供的浅灰色文本 是为了帮助用户理解在文本栏中能够输入何种信 息。在运行的应用程序中,用户只要在文本栏内 轻按鼠标,占位符文本就会立即消失。 步骤9  在 Text Field Attributes 检查器中, 单击中间的“Alignment”按钮,使文本栏的文本 居中显示。 步骤 10 在视图中,拖移标签到文本栏下 方,并使其左边缘和文本栏的左边缘对齐。 步骤 11 调整标签的宽度。 拖移标签右侧的控制柄,使标签与文本栏 同宽。比起文本栏,标签有更多的控制柄用于调 整标签的高度和宽度。现在并不是要更改标签的 高度,因此不要拖移标签四个角的控制柄。要拖 移标签右侧中间的控制柄调整宽度,如图 2-19 所示。 步骤 12 在 Label Attributes 检查器中,单击 中间的“Alignment”按钮,使出现在标签中的文 本居中显示。 步骤 13 拖移按钮使其靠近视图底部并且水平居中。 步骤 14 在画布上,双击该按钮,然后输入文本 Hello。 添加文本栏、标签和按钮 UI 元素,并对布局做出建议的修改后,如图 2-20 所示。 可能注意到,当将文本栏、标签和按钮添加到背景视图时,Xcode 在 Constraints 大纲视 图中插入了项目。Cocoa Touch 具有一个自动布局系统,用来定义用户界面元素的布局约束。 这些约束定义用户界面元素之间的关系,当其他视图的大小改变或设备摆放方向改变时,该 关系影响各界面元素如何改变其位置和几何形状。现在不要改变添加到用户界面的视图的默 认约束。 此外,可以对文本栏进行修改,使文本栏的行为符合用户的期望。首先,因为用户要 输入自己的姓名,确保 iOS 对用户键入的每个英文单词执行首字母大写。其次,确保与文 本栏相关联的键盘配置为输入姓名(而不是数字),并且键盘显示“Done”按钮。 图 2-18 需要调整大小的文本栏 㬚䇤⪬⮘䎜⫔㾂㋹䐧⢛ ⮘䎜⢋㣊⭥㌎Ⱙ Label 图 2-19 调整标签的宽度 50   第一部分 准 备 篇 图 2-20 应用程序界面布局图 注意 这些更改所遵循的原则是:因为在设计时已知道文本栏将包含何种类型的信息,可以 配置文本栏使它运行时的外观和行为符合用户的任务。这些配置都可以在 Attributes 检查器 中修改。 2. 对文本栏进行配置 对文本栏进行配置的方法很简单。首先在视图中选择文本栏,在 Text Field Attributes 检 查器中进行以下选择。 ‰‰ 在“Capitalization”弹出式菜单中选取“Words”。 ‰‰ 确保“Keyboard”弹出式菜单设定为“Default”。 ‰‰ 在“Return Key”弹出式菜单中选取“Done”。 3. 运行应用程序 在 Simulator 中运行应用程序,以确定所添加的 UI 元素外观正如所期望的样子。如果单 第 2 章 创建你的第一个 iOS 应用程序   51 击“Hello”按钮,该按钮应该高亮显示 ;如果在文本栏内单击,键盘应该出现。不过,这时 按钮没有任何功能,标签还是空的,而且键盘出现后无法使它消失。要添加此功能,需要在 UI 元素和视图控制器之间进行适当的连接。下面将说明如何建立连接。 注意 因为只是在 Simulator 中(而不是在设备上)运行应用程序,所以通过单击来激活控 制,而不是用手轻按的方式。 2.5.2 为按钮增添一个动作 当用户激活一个 UI 元素时,该元素可以向知道如何执行相应操作方法的对象发送一则 操作消息,例如“将此联系人添加到用户的联系人列表”。这种互动是目标 - 操作机制的一部 分,该机制是另一种 Cocoa Touch 设计模式。 在本节中,当用户单击“Hello”按钮时,想要发送一则“更改问候语”的消息(操作) 给视图控制器(目标)。视图控制器通过更改其管理的字符串(即模型对象)来响应此消息。 然后,视图控制器更新在标签中显示的文本,以反映模型对象值的变动。 使用 Xcode,可以将操作添加到 UI 元素,并设置其相应的操作方法。方法是按住 Ctrl 键并将画布上的元素拖移到源文件中的合适位置(通常是类扩展在视图控制器的实现文件 中)。串联图将通过这种方式创建的连接归档存储下来。应用程序在载入串联图时会恢复这 些连接。 为按钮添加动作的步骤如下。 步骤 1 如有需要,选择项目导航器中的 MainStoryboard.storyboard,将场景显示在画布上。 步骤 2 在 Xcode 工具栏中,单击“Utilities”按钮以隐藏实用工具区域,单击“Assistant Editor”按钮以显示辅助编辑器面板。“Assistant Editor”按钮为中间的编辑器按钮。 步骤 3 确定“Assistant”显示视图控制器的实现文件,即 HelloWorldViewController.m。 如果显示 HelloWorldViewController.h,在项目导航器中选择 HelloWorldViewController.m。 步骤 4 对象连接。 在画布上按住 Ctrl 键将“Hello”按钮拖移到 HelloWorldViewController.m 中的类扩展。 实现文件中的类扩展是申明类的专有属性和方法的地方。视图控制器的 Xcode 模板包含 实现文件中的类扩展。以 HelloWorld 项目为例,类扩展看起来像这样: @interface HellowWorldViewController() @end 按住 Ctrl 键不放,将按钮拖移到辅助编辑器中的实现文件。随着你按住 Ctrl 键拖移,看 到的应该如图 2-21 所示。 松开 Ctrl 键停止拖移后,Xcode 会显示一个弹出式窗口,在窗口中可以设置操作连接, 如图 2-22 所示。 52   第一部分 准 备 篇 图 2-21 将按钮拖移到辅助编辑器中的实现文件 图 2-22 对象连接 注意 如果在 HelloWorldViewController.m 类扩展区域以外的其他地方松开 Ctrl 键并停止拖 移,可能会看到不同类型的弹出式窗口,或者什么都没有。如果出现这种情况,要在画布上 的视图内部单击以关闭弹出式窗口,并再试一次按住 Ctrl 键拖移。 步骤 5 绑定处理。 在弹出式窗口中,按如下操作配置按钮的操作连接。 ‰‰ 在“Connection”弹出式菜单中选取“Action”。 ‰‰ 在“Name”栏中输入“changeGreeting:”(包括冒号)。在后面步骤中将实施该方法, 让它把用户输入文本栏的文本载入,然后在标签中显示。 ‰‰ 确定“Type”栏包含 id。id 数据类型可指任何 Cocoa 对象。在这里使用 id 是因为无 第 2 章 创建你的第一个 iOS 应用程序   53 论哪种类型的对象发送消息都没有关系。 ‰‰ 确定“Event”弹出式菜单包含“Touch Up Inside”。指定“Touch Up Inside”事件是 因为想在用户触摸按钮后提起手指时发送消息。 ‰‰ 确定“Arguments”弹出式菜单包含“Sender”。 配置完操作连接后,弹出式窗口如图 2-23 所示。 图 2-23 绑定处理 步骤 6 在弹出式窗口中单击“Connect”。 Xcode 为新的 changeGreeting: 方法添加一个存根实现,并在该方法的左边显示一个带有 填充的圆圈,以标示已经建立连接。 步骤 7 按住 Ctrl 键将“Hello”按钮拖移到 HelloWorldViewController.m 文件中的类扩展。 目前,你完成两件事情 :通过 Xcode 将合适的代码添加到视图控制器类中(也就是 HelloWorldViewController.m 中),并在按钮和视图控制器之间创建了连接。具体来说,Xcode 做了以下事情。 1)在 HelloWorldViewController.m 中,将以下操作方法声明添加到类扩展。 - (IBAction)changeGreeting:(id)sender; 2)将以下存根方法添加到实现区域。 - (IBAction)changeGreeting:(id)sender { } 注意 IBAction 是一个特殊关键词,用于告诉 Xcode 将一个方法作为目标 - 操作连接的操作 部分来处理。IBAction 被定义为 void。 操作方法中的 sender 参数指向发送操作消息的对象(在本节中,发送对象为按钮)。它 在按钮和视图控制器之间创建了连接。 接下来,在视图控制器和其他两个 UI 元素(即标签和文本栏)之间创建连接。 2.5.3 为文本框和标签创建插座 插座(Outlet)表明了两个对象之间的连接。当一个对象(例如视图控制器)要和它包 含的对象(例如文本栏)进行通信时,必须将被包含的对象指定为 Outlet。应用程序在运行 54   第一部分 准 备 篇 时会恢复在 Xcode 中创建的 Outlet,从而使对象在运行时可以互相通信。 在本节中,将视图控制器从文本栏获取用户的文本,然后将文本显示在标签上。为确保 视图控制器可以和这些对象通信,就必须在它们之间创建 Outlet 连接。 为文本栏和标签添加 Outlet 的步骤与添加按钮操作的步骤非常相似。开始之前,要确定 主串联图文件仍然显示在画布上,而 HelloWorldViewController.m 在辅助编辑器中保持打开。 1. 为文本栏添加 Outlet 为文本栏添加 Outlet 的步骤如下。 步骤 1 按住 Ctrl 键将视图中的文本栏拖移到实现文件中的类扩展。 随着按住 Ctrl 键进行拖移,会看到如图 2-24 所示的窗口。 图 2-24 为文本栏添加 Outlet 在类扩展内部的任何地方松开 Ctrl 键并停止拖移都没有关系。在本节中,文本栏和标签 的 Outlet 声明出现在“Hello”按钮的方法声明上。 步骤 2 为文本设置 Outlet 弹出窗口。 在松开 Ctrl 键并停止拖移时出现的弹出式窗口中,按如下操作配置文本栏的连接。 ‰‰ 确定“Connection”弹出式菜单包含“Outlet”。 ‰‰ 在“Name”栏中键入“textField”。可以为 Outlet 随意命名,但是如果其名称与其表 示的项目有关联,会使代码更便于阅读。 第 2 章 创建你的第一个 iOS 应用程序   55 ‰‰ 确 定“Type”栏包含“UITextField”。 将 “ Type”栏设定为“UITextField”可确保 Xcode 将 Outlet 仅与文本栏连接。 ‰‰ 确定“Storage”弹出式菜单包含默认值“Weak”。 完成这些设置后,将会看到如图 2-25 所示的弹出式窗口。 图 2-25 为文本设置 Outlet 后的弹出窗口 步骤 3 在弹出式窗口中,单击“Connect”。 在这个过程中,通过为文本栏添加 Outlet,你完成了两件事情。 1)Xcode 将合适的代码添加到视图控制器类的实现文件(HelloWorldViewController.m)。 具体来说,Xcode 将以下声明添加到类扩展。 @property (weak, nonatomic) IBOutlet UITextField *textField; 注意 IBOutlet 是一个特殊关键词,仅用于告诉 Xcode 将对象作为 Outlet 处理。它实际上被 定义为什么都不是,因此在编译时不起作用。 2)Xcode 在视图控制器和文本栏之间建立了连接。 通过在视图控制器和文本栏之间建立连接,用户输入的文本可以传递给视图控制器。和 处理 changeGreeting: 方法声明一样,Xcode 在文本栏声明的左边显示带有填充的圆圈,以表 示已经建立连接。 注意 较早版本的 Xcode 使用的方式是 :按住 Ctrl 键拖移,将 @synthesize 指令添加到所声 明的每个属性的实现块。因为编译器自动合成存取方法,所以这些指令是不必要的,可以放 心地将它们全部删除。 2. 为标签添加 Outlet 现在为标签添加 Outlet 并配置连接。在视图控制器和标签之间建立连接,会让视图控制 器以字符串(该字符串包含用户输入的文本)来更新标签。完成此任务的步骤与为文本栏添 加 Outlet 的步骤相同,只不过在配置时要做适当修改(确定 HelloWorldViewController.m 仍 然显示在辅助编辑器中)。具体步骤如下。 步骤 1 按住 Ctrl 键将视图中的标签拖移到辅助编辑器中的 HelloWorldViewController.m 的 类扩展。 步骤 2 为标签设置 Outlet 弹出窗口。 56   第一部分 准 备 篇 在松开 Ctrl 键并停止拖移时出现的弹出式窗口中配置标签的连接。 ‰‰ 确定“Connection”弹出式菜单包含“Outlet”。 ‰‰ 在“Name”栏中键入“label”。 ‰‰ 确定“Type”栏包含“UILabel”。 ‰‰ 确定“Storage”弹出式菜单包含“Weak”。 步骤 3 在弹出式窗口中,单击“Connect”。 2.5.4 打开 Connections 检查器验证连接 到此为止,一共创建了三种到视图控制器的连接 :1)按钮的操作连接 ;2)文本 栏的 Outlet 连接 ;3)标签的 Outlet 连接。可以在 Connections 检查器中验证这些连接 (图 2-26)。 图 2-26 Connections 检查器的设置 为视图控制器打开 Connections 的步骤如下。 步骤 1 单击标准编辑器按钮以关闭辅助编辑器,并切换到标准编辑器视图。标准编辑 器按钮是最左边的“Editor”按钮 。 步骤 2 单击“Utilities”视图按钮打开实用工具区域。 步骤 3 在大纲视图中选择“Hello World View Controller”。 步骤 4 在实用工具区域显示 Connections 检查器。点击检查器选择栏最右边的按钮,图标 是 。 第 2 章 创建你的第一个 iOS 应用程序   57 在 Connections 检查器中,Xcode 显示了所选对象(在本例中为视图控制器)的连接。 如图 2-26 所示。可以发现,在视图控制器和其视图之间,除了创建的三个连接之外,还有一 个连接。Xcode 提供了视图控制器和其视图之间的默认连接,不必用任何方式访问它。 2.5.5 对文本框进行委托处理 还需要在应用程序中建立另一个连接,即将文本栏连接到指定的委托对象上。本节将视 图控制器用作文本栏的委托。 首先需要为文本栏指定一个委托对象。因为当用户轻按键盘中的“Done”按钮时,文本 栏发送消息给它的委托(委托是代表另一个对象的对象)。在后面的步骤中,将使用与此消 息相关联的方法让键盘消失。 注意 确定串联图文件已在画布上打开。否则在项目导航器中选择 MainStoryboard.storyboard。 设定文本栏的委托的方法如下。在视图中,按住 Ctrl 键将文本栏拖移到场景台中的黄色 球体(黄色球体代表视图控制器对象)松开 Ctrl 键并停止拖移时,将会看到如图 2-27 所示的 界面。 图 2-27 设定文本栏的委托 58   第一部分 准 备 篇 在出现的半透明面板的“Outlets”部分中选择“delegate”,以达到关联对象的作用。 2.5.6 让应用程序具有辅助功能 Apple 的创新性读屏技术 VoiceOver 是一个重要的辅助功能。使用 VoiceOver,用户可 以在不看屏幕的情况下导航和控制应用程序的各部分。通过触摸用户界面中的控制或其他对 象,用户可以知道它们的位置、可以执行的操作以及执行某些操作后将发生什么。 可以将一些辅助功能属性添加到用户界面中的任何视图。这些属性包括视图的当前值 (例如文本栏中的文本)、标签、提示以及其他特征。就 HelloWorld 应用程序而言,我们将要 给文本栏添加一个提示功能。 注意 所添加的任何辅助功能文本都应该本地化。 添加辅助功能的步骤如下。 步骤 1 在项目导航器中选择串联图文件(Base Internationalization)。 步骤 2 选择文本栏进行关联。 步骤 3 在 Identity 检查器“Accessibility”部分的 Hint 栏中键入提示内容。 如图 2-28 所示,其中 Hint 栏中键入“Type your name”,这是光标移动到文本栏时,将 会自动提示的内容。 图 2-28 添加辅助功能 步骤 4 单击“Run”测试应用程序。 单击“Hello”按钮时,应该看到它高亮显示。如果在文本栏中点按击会出现键盘,可以 输入文本,但还没有办法让键盘消失。要让键盘消失,必须实施相关的委托方法。 第 2 章 创建你的第一个 iOS 应用程序   59 2.6 使用视图控制器完成应用程序 使用视图控制器包括这几部分:1)为用户姓名添加属性;2)实现 changeGreeting: 方法; 3)把视图控制器作为输入文本框的委托,确保用户轻按“Done”时键盘消失。 2.6.1 给用户的名称添加属性 只有为保存用户姓名的字符串添加属性声明,你的代码才能引用该字符串。因为此属性 必须是公共的,即对客户端和子类为可见,所以要将此声明添加到视图控制器的头文件(即 HelloWorldViewController.h)。公共属性表示你打算如何使用这一类的对象。 属性声明是一个指令,它告诉编译器如何为变量(例如用来保存用户姓名的变量)生成 存取方法。(添加属性声明后,你将了解到有关存取方法的信息。) 到此为止,不需要对串联图文件做出任何进一步的修改。要腾出更多空间以按照 以下步骤来添加代码,再次单击“Utilities View”按钮来隐藏实用工具区域(或者选取 “View”→“Utilities”→“Hide Utilities”)。 为用户姓名添加属性声明的步骤如下。 在项目导航器中选择 HelloWorldViewController.h。在 @end 语句前,为字符串编写一个 @property 语句。属性声明应该是这样的: @property (copy, nonatomic) NSString *userName; 可以复制和粘贴以上代码,也可以在编辑器面板中键入以上代码。如果你决定键入 代码,注意,Xcode 会根据键入内容提供自动补齐的建议。例如,开始键入“@prop...”, Xcode 猜测你想要键入 @property,因此会在内联建议面板中显示一个符号,如图 2-29 所示。 图 2-29 属性的声明 如果建议合适,按下 Return 键接受建议。随着你的继续键入,Xcode 可能提供一个建议 列表供你选取。例如键入“NSSt...”, Xcode 可能显示如图 2-30 所示的补齐列表。 当 Xcode 显示补齐列表时,按下 Return 键以接受高亮显示的建议。如果高亮显示的建 议不正确,可使用箭头键从列表中选择合适的项目。 编译器自动为你声明的任何属性合成“存取方法”。存取方法是一种获取或设定一个对 象属性值的方法(因此,存取方法也称为“getter”和“setter”)。例如,编译器为刚刚声明 的 userName 属性生成以下 getter 和 setter 声明及其实现: - (NSString *)userName; - (void)setUserName:(NSString *)newUserName; 60   第一部分 准 备 篇 图 2-30 Xcode 提供的建议列表 编译器自动声明专有实例变量以支持每一个声明的属性。例如,编译器声明 _userName 实例变量以支持 userName 属性。 注意 编译器将生成的存取方法添加到编译代码,而不是添加到你的源代码中。 2.6.2 实现 changeGreeting: 方法 之前我们已经配置了“Hello”按钮,在用户轻按该按钮时,发送 changeGreeting: 消息 给视图控制器。作为响应,需要视图控制器将用户在文本栏中输入的文本显示在标签中。具 体来说,changeGreeting: 方法基本实现机制如下。 ‰‰ 从文本栏取回字符串,并将视图控制器的 userName 属性设定为此字符串。 ‰‰ 基于 userName 属性创建新的字符串,并将其显示在标签中。 实施 changeGreeting: 方法的步骤如下。 步骤 1 如有需要,在项目导航器中选择 HelloWorldViewController.m。 可能需要滚动到文件的末尾才能看到 changeGreeting: 存根实现,它是 Xcode 添加的。 步骤 2 添加以下代码完成 changeGreeting: 方法的存根实现。 - (IBAction)changeGreeting:(id)sender { self.userName = self.textField.text; NSString *nameString = self.userName; if ([nameString length] == 0) { nameString = @"World"; } NSString *greeting = [[NSString alloc] initWithFormat:@"Hello, %@!",nameString]; self.label.text = greeting; } changeGreeting: 方法中有几项值得注意。 第 2 章 创建你的第一个 iOS 应用程序   61 1)self.userName=self.textField.text; 从文本栏取回文本,并将视图控制器的 userName 属 性设定为该结果。 在本节中,在其他任何地方都不会用上那个保存着用户姓名的字符串,但重要的是记住 它的角色 :这正是视图控制器所管理的非常简单的模型对象。一般情况下,控制器应在它自 己的模型对象中维护应用程序数据的相关信息。换句话说,应用程序数据不应储存在用户界 面元素(例如 HelloWorld 应用程序的文本栏)中。 2)NSString *nameString = self.userName; 创建一个新的变量(为 NSString 类型)并将 其设为视图控制器的 userName 属性。 3)@"World" 是一个字符串常量,用 NSString 的实例表示。如果用户运行应用程序但不 输入任何文本(即 [nameString length] == 0), nameString 将包含字符串 "World"。 4)initWithFormat: 方法是由 Foundation 框架提供的。作用是按提供的格式字符串 所规定的格式创建一个新的字符串(很像 ANSI C 库中的 printf 函数)。在格式字符串 中,%@ 充当字符串对象的占位符。此格式字符串的双引号中的所有其他字符都将如实显 示在屏幕上。 2.6.3 把视图控制器作为输入文本框的委托 如果生成并运行应用程序,在单击按钮时应该会看到标签显示“Hello,World!”。如果 你选择文本栏并开始在键盘上键入,会发现完成文本输入后,仍然无法让键盘消失。 在 iOS 应用程序中,允许文本输入的元素成为第一响应器时,键盘会自动出现 ;元素 失去第一响应器状态时,键盘会自动消失。(前面提到过第一响应器是第一个接收各种事 件通知的对象,例如轻按文本栏来调出键盘。)虽然无法从应用程序直接将消息发送给键 盘,但是可以通过切换文本输入 UI 元素的第一响应器状态这种间接方式,使键盘出现或 消失。 UITextFieldDelegate 协议是由UIKit 框架定义的,它包括textFieldShouldReturn: 方法, 当用户轻按“Return”按钮(不管该按钮的实际名称是什么)时,文本栏调用该方法。因为 已经将视图控制器设定为文本栏的委托,可以实施该方法,通过发送 resignFirstResponder 消 息强制文本栏失去第一响应器状态,利用该方法的副作用使键盘消失。 注意 协议基本上只是一个方法列表。如果一个类符合(或采用)某个协议,则需要保证它 可以实施该协议所要求的方法(协议也可以包括一些可选的方法)。委托协议指定了一个对 象可能向其委托发送的所有消息。 将 HelloWorldViewController 配置为文本栏的委托的步骤如下。 步骤 1 如有需要,在项目导航器中选择 HelloWorldViewController.m。 步骤 2 实施 textFieldShouldReturn: 方法。 此方法应该指示文本栏放弃第一响应器的状态。实现结果是: - (BOOL)textFieldShouldReturn:(UITextField *)theTextField { 62   第一部分 准 备 篇 if (theTextField == self.textField) { [theTextField resignFirstResponder]; } return YES; } 在本应用程序中,没有必要真正测试 theTextField == self.textField 表达式,因为只有一 个文本栏。但这是一个很好的模式,因为有些场合你的对象可能不只是一个同类对象的委 托,所以可能需要对它们加以区分。 步骤 3 在项目导航器中选择 HelloWorldViewController.h。在 @interface 行的末尾添加 。接口声明应如下: @interface HelloWorldViewController :UIViewController 此声明指定 HelloWorldViewController 类采用 UITextFieldDelegate 协议。 2.7 测试应用程序 生成并运行应用程序。现在,一切的表现都应该如你所期望的那样。在 Simulator 中, 输入你的姓名后,单击“Done”按钮使键盘消失,然后单击“Hello”按钮将在标签中显示 “Hello,你的姓名 !”。如果应用程序的表现不是你所期望的,需要进行故障排除。 2.7.1 排查和检测代码 如果应用程序未能正确工作,可尝试本章讲述的解决问题方法。如果应用程序仍然不能 正确工作,可将你的代码与本章末尾给出的清单进行比较。 1. 查看代码编译器警告 代码编译时应该不会有任何警告。如果真的收到警告,就很有可能是代码出错了。因为 Objective-C 是一种非常灵活的程序设计语言,有时候编译器给出的也仅仅是一些警告而已。 2. 检查 Storyboard 文件 如果程序未能正确工作,开发者会很自然地去检查源代码来找出错误。但使用 Cocoa Touch 又增添了另一个层面的问题 :应用程序的大部分配置可能是“编码”在串联图中。 例如,如果连接不正确,应用程序的行为就会与你的期望不符。 如果单击按钮时文本没有更新,可能是没有将按钮的操作连接到视图控制器,或是没有 将视图控制器的 Outlet 连接到文本栏或标签。 如果单击“Done”按钮时键盘不消失,可能是没有将文本栏的委托连接好,或者把视图 控制器的 textField Outlet 连接到了文本栏。务必在串联图上检查文本栏的连接,按住 Ctrl 键 单击文本栏以显示半透明的连接面板。在 delegate outlet 和 textField 引用 Outlet 的旁边应该 看到圆圈。 第 2 章 创建你的第一个 iOS 应用程序   63 3. 检查委托方法命名 与委托有关的一个常见错误是拼错委托方法的名称。即使已经正确设定了委托对象,但 是如果委托未在其方法实现中使用正确的名称,则不会调用正确的方法。通常最好的做法是 从文稿中复制和粘贴委托方法声明(例如 textFieldShouldReturn:)。 2.7.2 程序代码清单 本节提供 HelloWorldViewController 类的接口和实现文件的代码清单。注意,该代码清 单并未列出 Xcode 模板提供的注释和其他方法的实现。 代码清单 2-1 接口文件 HelloWorldViewController.h #import @interface HelloWorldViewController :UIViewController @property (copy, nonatomic) NSString *userName; @end 代码清单 2-2 实现文件 HelloWorldViewController.m #import "HelloWorldViewController.h" @interface HelloWorldViewController () @property (weak, nonatomic) IBOutlet UITextField *textField; @property (weak, nonatomic) IBOutlet UILabel *label; - (IBAction)changeGreeting:(id)sender; @end @implementation HelloWorldViewController - (void)viewDidLoad{ [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); } - (IBAction)changeGreeting:(id)sender { self.userName = self.textField.text; NSString *nameString = self.userName; if ([nameString length] == 0) { nameString = @"World"; } NSString *greeting = [[NSString alloc] initWithFormat:@"Hello, %@!", nameString]; self.label.text = greeting; } - (BOOL)textFieldShouldReturn:(UITextField *)theTextField { if (theTextField == self.textField) { 64   第一部分 准 备 篇 [theTextField resignFirstResponder]; } return YES; } @end 2.8 小结 通过本章的讲解,你一定会对开发 iPhone 应用程序有所了解,进而对基础开发过程有初 步了解。但是要开发出优秀的 iPhone 应用程序,还有很多的知识需要掌握和学习,后面将会 逐步介绍。 第二部分 语 法 篇 ‰ 第 3 章 Objective-C——构建 iOS 应用程序的基石 ‰ 第 4 章 类——构建应用程序的类型对象原型 ‰ 第 5 章 对象——构建应用程序的重要“活体” ‰ 第 6 章 消息和协议——对象之间的通信方式 ‰ 第 7 章 Foundation 框架——提供基本的系统服务 ‰ 第 8 章 内存管理——应用程序高效运行的基础 第 3 章 Objective-C——构建 iOS 应用程序的基石 “工欲善其事,必先利其器。”第 1 章简单介绍了 Objective-C 一些发展情况及特性,对 于 Objective-C 有了初步的了解。本章将从整体上,继续介绍一些 Objective-C 特性,进一步 加深了解 Objective-C。后面的几章中也将围绕 Objective-C 的某一点做进一步的介绍。 注意 建议在阅读本章之前,先熟悉一下 C 语言的数据类型、运算符以及程序控制语句。 “磨剑”的过程是一个漫长难熬的过程。无论学习任何一门技术,都需要能静下“心”, 让自己慢慢进入“境界”而入其“道”。对于学习 iOS 应用程序开发也不例外,也需要让自 己的“心”静下来,在“嚯嚯”之中,让自己的剑越来越锋利。 3.1 探窥 Objective-C 语言 Objective-C 是一种简洁的、面向对象的程序设计语言,所有 iOS 应用程序都由它来驱 动。你需要编写 Objective-C 代码来创建应用程序,同时需要懂得该语言,才能使用大多数 的框架。尽管你可以使用其他编程语言来开发,但不使用 Objective-C 就无法生成 iOS 应用 程序(图 3-1)。 图 3-1 Objective-C 语言与 iOS 应用程序 第 3 章 Objective-C——构建 iOS 应用程序的基石    67 Objective-C 的语法和规范简单易学。如果你有其他面向对象程序设计语言(例如 Java 或 C++)的编程经验,那么它对你来说更容易上手。如果你是 C 语言程序员,会发现熟悉面 向对象编程和 Objective-C 后,应用程序的设计和修改变得更加容易。 3.1.1 面向对象语言 Objective-C 是 C 语言的超集 Objective-C 程序设计语言采用特定的语法,来定义类和方法、调用对象的方法、动态地 扩展类,以及创建编程接口来解决具体问题。Objective-C 作为 C 程序设计语言的超集,支持 与 C 相同的基本语法。你会看到所有熟悉的元素,例如基本类型(int、float 等)、结构、函 数、指针,以及流程控制结构,如 if…else 语句和 for 语句。你还可以访问标准 C 库例程, 例如在 stdlib.h 和 stdio.h 中声明的那些例程。 Objective-C 为 ANSI C 添加了下述语法和功能。 ‰‰ 定义新的类; ‰‰ 类和实例方法; ‰‰ 方法调用(称为发消息); ‰‰ 属性声明(以及通过它们自动合成存取方法); ‰‰ 静态和动态类型化; ‰‰ 块(block),已封装的、可在任何时候执行的多段代码; ‰‰ 基本语言的扩展,如协议和类别。 如果现在还不熟悉 Objective-C 的相关知识,不必担心。读完本节,你将逐渐了解。如 果你是过程化程序开发人员,不懂面向对象的概念,建议先将对象从本质上视为具有关联 函数的结构,可能会有助于理解。这个概念与事实差不多,特别是在运行时实现方面。 Objective-C 程序设计语言能用来进行复杂的、面向对象的编程。通过提供用于定义类和 方法的语法,扩展了标准的 ANSI C 程序设计语言。它还促进类和接口(任何类可采用)的 动态扩展。如果使用其他面向对象程序设计语言进行过编程,你会发现许多传统的面向对象 概念,例如封装、继承、多态,都出现在 Objective-C 中。 除了提供在其他面向对象语言中已有的多数抽象和机制之外,Objective-C 还是一种非 常动态的程序设计语言,这种动态是其最大优势。这种动态体现在它允许在运行应用程序时 (即运行时)才去确定其行为,而不是在生成期间就已固定下来。因此,Objective-C 的动态 机制让程序免受约束(编译和链接程序时施加的约束);进而在用户控制下,将大多数符号 解析责任转移到运行时。 3.1.2 类和对象 如同其他大多数面向对象语言那样,Objective-C 中的类支持数据的封装,定义对这些 数据执行的操作。对象是类的运行时实例,它包含自己的实例变量(由其类声明)的内存副 本,以及类方法的指针。可以采用两步法(分配和初始化)创建对象。 68   第二部分 语 法 篇 Objective-C 中某个类的规格需要两个部分:接口和实现。 接口部分包含类声明,并定义该类的公共接口。如同 C 代码那样,定义头文件和源代码 文件,将公共声明与代码的实现细节分开(如果其他声明是编程接口的一部分,并且打算专 由你将它们放在实现文件中)。这些文件的扩展名如表 3-1 所示。 表 3-1 文件扩展名 扩展名 源类型 描  述 h 头文件 包含类、类型、函数和常量声明 m 实现文件 此类型文件可以同时包含 Objective-C 代码和 C 代码,有时也称为源文件 mm 实现文件 此类型文件,除了包含 Objective-C 代码和 C 代码以外,还可以包含 C++ 代码。 仅当实际引用你的 Objective-C 代码中的 C++ 类或功能时,才使用此扩展名 当你想要在源代码中包括头文件时,请在头文件或源文件的前几行之中,指定一个导入 (#import)指令。#import 指令类似于 C 的 #include 指令,前者确保同一文件只被包括一次。 如果要导入框架的大部分或所有头文件,请导入框架的包罗头文件(umbrella header file), 它具有与框架相同的名称。例如,导入 Gizmo 框架头文件的语法是: #import 图 3-2 所示为 MyClass 类的语法声明,它是从基础类(或根类)NSObject 继承而来的 (根类是供其他类直接或间接继承的类)。类声明以编译器指令 @interface 开始,以 @end 指令结束。类名称后面是父类名称,以冒号分隔。Objective-C 中,一个类只能有一个父类。 ㏁㘜⧧ ⶙㏁㘜⧧ ⧪䊒⢅㑠 㪚㘘 Ⳟⳉ㪚㘘 图 3-2 MyClass 类的声明 在 @interface 指令和 @end 指令之间,编写属性和方法的声明。这些声明组成了类的公 共接口。分号标记每个属性和方法声明的结尾。如果类具有与其公共接口相关的自定函数、 常量或数据类型,请将它们的声明放在 @interface…@end 块之外。 类实现的语法是相似的,以 @implementation 编译器指令开始(接着是该类的名称),以 @end 指令结束,中间是方法实现(函数实现应在 @implementation…@end 块之外)。一个实 现应该总是将导入它的接口文件作为代码的第一行。 第 3 章 Objective-C——构建 iOS 应用程序的基石    69 下面是 MyClass 类的定义代码。 #import "MyClass.h" @implementation MyClass - (id)initWithString:(NSString *)aName{ // code goes here } + (MyClass *)myClassWithString:(NSString *)aName{ // code goes here } @end 对于包含对象的变量,Objective-C 既支持动态类型化,也支持静态类型化。静态类型化 的变量,要在变量类型声明中包括类名称。动态类型化的变量,则要给对象使用类型 id。在 某些情况下,需要使用动态类型化的变量。例如,集(collection)对象、数组,在它包含对 象的类型未知的情况下,可能会使用动态类型化的变量。此类变量提供了极大的灵活性,也 让 Objective-C 程序拥有了更强大的活力。 下面的例子展示了静态类型化和动态类型化的变量声明。 MyClass *myObject1; // Static typing id myObject2; // Dynamic typing NSString *userName; // From Your First iOS App (static typing) 注意 第一个声明中的星号(*), 在 Objective-C 中,执行对象引用的只能是指针。如果你 还不能完全理解这个要求,不用担心。并非一定要成为指针专家才能开始 Objective-C 编程。 只需要记住,在静态类型化的对象的声明中,变量的名称前面应放置一个星号(*)。 id 类型 意味着一个指针。 3.1.3 方法和发消息 如果你不熟悉面向对象编程,则将方法想象成一个规范特定对象的函数,可能会有 所帮助。通过将一则消息发送到(或发消息给)一个对象,可调用该对象的一个方法。 Objective-C 中有两种类型的方法。 ‰‰ 实例方法 :由类的实例来执行。在调用实例方法之前,必须先创建该类的实例。实例 方法是最常见的方法类型。 ‰‰ 类方法:可由它所在的类直接执行。不需要对象的实例作为消息的接收者。 方法声明包含方法类型标识符、返回类型、一个或多个签名关键词,以及参数类型和名 称信息。图 3-3 是 insertObject:atIndex: 实例方法的声明。 对于实例方法,声明前面是减号(-);对于类方法,对应指示器是加号(+)。 70   第二部分 语 法 篇 Ⳟⳉ㏁㾮 ⢋㬗ⴜ Ⳗ⿹㏁㾮 Ⳟⳉ㣊㘜⹹ポ⪫ ⤯㭞㏁㾮 ⤯㭞㘜 图 3-3 insertObject:atIndex: 实例方法的声明 一个方法的实际名称(insertObject:atIndex:)是所有签名关键词的串联,包括冒号字符。 冒号字符表明有参数存在。在上述示例中,该方法采用两个参数。如果方法没有参数,则省 略第一个(也是仅有的一个)签名关键词后面的冒号。 想要调用一个方法时,通过给实施该方法的对象发送一则消息来实现(虽然“发送消 息”常用作“调用方法”,但实际上,Objective-C 在运行时才会执行实际地发送)。消息包含 方法名称,以及方法所需的参数信息(类型要匹配)。发送到一个对象的所有消息都被动态 地分派,这样使 Objective-C 类的多态行为更加容易(多态性是指不同类型的对象响应同一 消息的能力)。有时被调用的方法是由接收消息对象的类之超类来实现。 分派消息,运行时需要一个消息表达式。消息表达式使用方括号([…])将消 息本身以及任何所需参数括起来,同时将接收消息的对象放在最前面。例如,要将 insertObject:atIndex: 消息发送给 myArray 变量保存的对象,使用以下语法: [myArray insertObject:anObject atIndex:0]; 为避免声明大量局部变量来储存临时结果,Objective-C 支持嵌套消息表达式。每个嵌套 表达式的返回值,都用作另一个消息的一个参数或接收对象。例如,可以将上个示例中使用 的任何变量替换为取回值的消息。因此,如果具有另一个名为 myAppObject 的对象,并且此 对象具有访问数组对象的方法以及要插入数组的对象,可以将上个示例编写为如下形式: [[myAppObject theArray] insertObject:[myAppObject objectToInsert] atIndex:0]; Objective-C 还提供用于调用存取方法的点记法语法。存取方法获取并设定对象的状 态,因此封装很重要,是所有对象的重要功能。对象隐藏或封装其状态,并显示接口,该 接口是访问该状态的所有实例都通用的。使用点记法语法,可以将上个示例重新编写为如 下形式: [myAppObject.theArray insertObject:myAppObject.objectToInsert atIndex:0]; 第 3 章 Objective-C——构建 iOS 应用程序的基石    71 还可以使用点记法语法进行赋值: myAppObject.theArray = aNewArray; 此语法只是编写 [myAppObject setTheArray:aNewArray]; 的另一种方式。在点记法表达 式中,不能使用对动态类型化的对象(类型为 id 的对象)引用。 对于类方法,尽管前几个示例将消息发送到了类的实例,也可以将消息发送到类本身。 (类是运行时创建的、类型为 Class 的对象。)向类发送消息时,指定的方法必须定义为类方 法,而非实例方法。类方法是一种功能,类似于 C++ 中的静态类方法。 类方法通常这样使用 :要么将类方法用作工厂方法创建类的新实例,要么访问与该类关 联的一些共享信息。类方法声明的语法与实例方法声明的语法相同,只是方法类型标识符使 用加号,而非减号。 以下示例说明如何将类方法用作类的工厂方法。在这种情况下,array 方法是 NSArray 类的类方法(被 NSMutableArray 继承),它分配并初始化类的新实例,并将其返回给代码。 NSMutableArray *myArray = nil;   // nil 可看做 NULL 的对象 myArray = [NSMutableArray array]; // 创建一个新数组,并把值赋给变量 myArray 3.1.4 属性和存取方法 属性通常是指某些由对象封装或储存的数据。它可以是标志(如名称或颜色),也可以 是与一个或多个其他对象的关系。一个对象的类定义一个接口,该接口使其对象的用户能获 取并设定所封装属性的值。执行这些操作的方法称为存取方法。 存取方法有两种类型,每个方法都必须符合命名约定。 ‰‰ getter 存取方法返回属性的值,名称与属性相同。 ‰‰ setter 存取方法设定属性的新值,形式为“setPropertyName:”,其中属性名称的第一 个字母大写。 Objective-C 提供已声明的属性作为一种方便的写法,用于存取方法的声明和实现。在首 个 iOS 应用程序中,声明了 userName 属性: @property (nonatomic, copy) NSString *userName; 使用已声明的属性后,就不必为该类中用到的每个属性实现 getter 和 setter 方法。而是 使用属性声明指定你想要的行为。编译器接着可以根据该声明创建或合成实际的 getter 和 setter 方法。已声明的属性减少了必须编写的样板文件代码量,使代码更简洁、出错机会更 少。使用已声明的属性或存取方法来获取和设定各项对象状态。 在类接口中包括方法声明和属性声明。在类的头文件中声明公共属性,而在源文件的类 扩展中声明专有属性。控制器对象(如委托和视图控制器)的属性通常应该为专有的。 属性的基本声明使用 @property 编译器指令,后面紧跟属性的类型信息和名称。还可以 使用自定选项来配置属性,以定义存取方法如何表现、属性是否为弱引用,以及是否为只 读。选项位于圆括号中,前面是 @property 指令。 72   第二部分 语 法 篇 属性声明的例子如下。 @property (copy) MyModelObject *theObject; // 赋值期间进行对象复制 @property (readonly) NSView *rootView; // 声明只可读 @property (weak) id delegate; // 声明 delegate 为弱引用 编译器自动合成所声明的属性。在合成属性时,它创建自己的存取方法,以及“支持” 该属性的专有实例变量。实例变量的名称与属性的名称相同,但具有下划线前缀(_)。 只 有在对象初始化和取消分配的方法中,应用程序才直接访问实例变量(而不是其属性)。 如果想让实例变量采用不同名称,可以绕过自动合成,并明确地合成属性。在类实现中 使用 @synthesize 编译器指令让编译器产生存取方法,以及进行特殊命名的实例变量。例如: @synthesize enabled = _isEnabled; 同时,在声明属性时,可以指定存取方法的自定名称,通常是使 Boolean 属性的 getter 方法遵循约定形式,如下所示: @property (assign, getter=isEnabled) BOOL enabled; 3.1.5 块 块是封装工作单元的对象,是可在任何时间执行的代码段。它们在本质上是可移植的匿 名函数,可作为方法和函数的参数传入,可从方法和函数中返回。 块本身具有一个已类型化的参数列表,且可能具有推断或声明的返回类型。还可以将块 赋值给变量,然后像调用函数一样调用它。 注意 块是从 iOS 4 开始引入的一个新特性,这从根本上改变你的编程方式。代码块是对 C 语言的一个扩展,因此在 Objective-C 中完全支持。如果你学过 Ruby、Python 或 Lisp 编程 语言,肯定知道代码块的强大之处。简单地说,可以通过代码块封装一组代码语句并将其当 作一个对象。代码块的使用是一种新的编码风格,可以轻松地使用 iOS 4 中新增的 API。 定义块的语法是使用一个插入符号(^),然后用小括号“(…)”把参数列(比如 int a, int b,NSString d) 包起来,接着用大括号“{…}”把行为的主体包起来,下面是块的基本语 法格式: ^{ ^( 传入参数列 ) { 行为主体 } ; } 插入符号(^)用作块的语法标记。块的参数、返回值和正文(即执行的代码)存在其 他类似的语法约定。图 3-4 解释了该语法。 可以调用块变量,就像调用函数一样: int result = myBlock(4); // result is 28 第 3 章 Objective-C——构建 iOS 应用程序的基石    73 “^”ㅌ⢅㑠myBlock 㪚㘘㸋䄜⷗㌊ 䄜⷗䓷㘇⧄㑠㌊Ⰹ 䅆᷍⳷㞅ⷙ⢅㑠 myBlock myBlock㬨䄜⷗㌊᷍ 。Ⳗ⿹int䐖 ⤪䇤䄜⷗⤯㭞᷍ 䄓㬨int㏁㾮 ⤯㭞㘜㸋num ㌊⭥䑘㳆⤠⳷ 图 3-4 块的语法解释 块共享局部词法作用范围内的数据。块的这项特征非常有用,因为如果实现一个方法, 并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及 函数和全局变量(包括实例变量)。这种访问是只读的,但如果使用 _block 修饰符声明变量, 则可在块内更改其值。即使包含有块的方法或函数已返回,并且其局部作用范围已销毁,但 是只要存在对该块的引用,局部变量仍作为块对象的一部分继续存在。 作为方法或函数参数时,块可用作回调。被调用时,方法或函数执行部分工作,并在适 当时刻,通过块回调正在调用的代码,从中请求附加信息,或获取程序特定行为。块使调用 方在调用时能够提供回调代码。块从相同的词法作用范围内采集数据(就像宿主方法或函数 所做的那样),而非将所需数据打包在“关联”结构中。由于块代码无须在单独的方法或函 数中实现,你的实施代码会更简单且更容易理解。 Objective-C 框架具有许多含块参数的方法。例如,Foundation 框架的 NSNotificationCenter 类声明以下方法,该方法具有一个块参数: -(id)addObserverForName:(NSString *)name object:(id)obj   queue:(NSOperationQueue *)queue   usingBlock:(void (^)(NSNotification *note))block 此方法将一个观察者添加到通知中心。以下代码指定名称的一则通知发布时,块被调用 以处理该通知。 opQ = [[NSOperationQueue alloc] init]; [[NSNotificationCenter defaultCenter] addObserverForName:@"CustomOperationCompleted" object:nil queue:opQ usingBlock:^(NSNotification *notif) { // handle the notification }]; 3.1.6 协议和类别 协议可声明由任何类实施的方法,即使实施该协议的那些类没有共同的超类。协议方法 74   第二部分 语 法 篇 定义了独立于任何特定类的行为。协议简单定义了一个由其他类负责实现的接口。当你的类 实施了一个协议的方法时,就是说该类符合该协议。 从实践角度而言,一个协议定义了一列方法,这些方法在对象之间建立合约,而无须使 那些对象成为任何特定类的实例。此合约使那些对象能够相互通信。一个对象想要告知另一 个对象它遇到的事件,或者它可能想要寻求有关那些事件的建议。 UIApplication 类实现一个应用程序必需的行为。UIApplication 类不是强制对 UIApplication 进行子类化,来接收有关应用程序当前状态的简单通知,而是通过调用指定给它的委托对象 的特定方法传送那些通知。实施 UIApplicationDelegate 协议的对象可以接收那些通知,并提 供适当的响应。 在接口块中,指定类符合或采用一个协议,方法是将该协议的名称放在尖括号(<…>)中,并 且放在它继承的类的名称后面。在你的首个 iOS 应用程序中,指明了采用 UITextFieldDelegate 协议,无须声明所实现的协议方法。代码行如下: @interface HelloWorldViewController :UIViewController 协议的声明,看起来类似于类接口的声明,只是协议没有父类,并且不定义实例变量 (尽管它们可以声明属性)。以下示例展示使用一个方法进行一个简单的协议声明。 @protocol MyProtocol - (void)myProtocolMethod; @end 对于许多委托协议来说,采用一个协议仅仅是实现该协议定义的方法。有些协议要求明 确地声明支持该协议,而协议可以指定必需方法和可选方法。 当你开始探索 Objective-C 框架的头文件时,将很快遇到如下的代码行: @interface NSDate (NSDateCreation) 这行代码通过使用圆括号将类别名称括起来的语法约定,声明了该类别。类别是 Objective-C 语言的一项功能,可扩展类的接口,而无须对类进行子类化。类别中的方法成为 类的类型的一部分(在程序的作用范围内),而这些方法由类的所有子类继承。可以将消息 发送给类(或其子类)的任何实例,以调用在类别中定义的方法。 可以将类别作为一种手段,对头文件内的相关方法声明进行分组。甚至还可以将不同 的类别声明放在不同的头文件中。框架在其所有头文件中使用这些技巧达到清晰明确的效 果。还可以使用称为类扩展的匿名类别,在实现文件(m)中声明专有属性和专有方法。类 扩展看起来类似于类别,只是圆括号之间没有文本。以下是一个典型的类扩展。 @interface MyAppDelegate () @property (strong) MyDataObject *data; @end 3.1.7 类型和编码策略 Objective-C 保留几个不能用作变量名称的术语,用于特殊用途。其中部分术语是以 第 3 章 Objective-C——构建 iOS 应用程序的基石    75 @ 符号为前缀的编译器指令,如 @interface 和 @end。其他保留的术语,包括已定义的类型 以及与这些类型相配的字面常量。Objective-C 使用很多已定义的类型和字面常量,这些却不 会出现在 ANSI C 中。在某些情况下,这些类型和字面常量替换 ANSI C 相应的类型和字面 常量。表 3-2 介绍几种重要类型,包括每种类型允许的字面常量。 表 3-2 常见类型和字面常量 类型 字面常量 描  述 id 动态对象类型 动态类型化对象和静态类型化对象的否定字面常量都是 nil Class 动态类类型 其否定字面常量是 nil SEL 选择器的数据类型(typedef) 运行时的方法签名。其否定字面常量是 NULL BOOL Boolean 类型 字面常量是 YES 和 NO 在错误检查和控制流代码中,通常使用已定义类型和字面常量。在程序的控制流语句 中,可以测试合适的字面常量来确定如何继续。例如: NSDate *dateOfHire = [employee dateOfHire]; if (dateOfHire != nil) { // handle this case } 以上代码如果表示雇用日期的对象不是 nil(换言之,如果它是一个有效对象),则逻辑 在某个方向继续。以下是执行相同分支的简写形式: NSDate *dateOfHire = [employee dateOfHire]; if (dateOfHire) { // handle this case } 可以进一步减少代码行数(假设无须引用 dateOfHire 对象): if ([employee dateOfHire]) { // handle this case } 根据返回的 Boolean 值是否为 YES 来判断实体是否相同。在下面的示例中,isEqual: 方 法返回一个 Boolean 值。 BOOL equal = [objectA isEqual:objectB];   if (equal == YES) {   // handle this case } 在 Objective-C 中,可以将消息发送到 nil,而没有副作用。事实上,完全没有影响,只 是如果方法应该返回一个对象,运行时就会返回 nil。只要返回的内容类型化为一个对象,即 可保证发送给 nil 的消息的返回值正常运行。 76   第二部分 语 法 篇 Objective-C 中其他两个重要的保留术语,是 self 和 super。self 是可在消息实现内使用的 局部变量,用于引用当前对象;它等同于 C++ 中的 this。可以用保留字 super 替换 self,但在 消息表达式中,只能作为接收者。如果你将消息发送到 self,运行时先在当前对象的类中查 找方法实现 ;如果在那里找不到方法,则在其超类中查找(以此类推)。如果你将消息发送 到 super,运行时先在超类中查找方法实现。 self 和 super 的主要用途都是发送消息。当要调用的方法是由 self 的类实现时,将消息 发送到 self。例如: [self doSomeWork]; self 还用于点记法,调用由已声明属性合成的存取方法。例如: NSString *theName = self.name; 在继承自超类的方法的覆盖(即重新实现)中,通常将消息发送到 super。在这种情况 下,被调用方法与被覆盖方法的签名,都是相同的。 3.1.8 import 语句 import 语句是在 Objective-C 语言中出现频率较高的一个语句。学习 import 语句是掌握 Objective-C 语言不可缺少的一部分,本节将围绕 import 语句展开详细的介绍。 1. 认识 #import #import 预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是 说一个被包含的文件中还可以包含其他文件。 预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样 就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如: #define AAA ##import "t.h" ##import AAA #include "t.h" 为了避免只能包含一次的头文件被多次包含,在头文件中用编译时条件进行控制。例如: /*my.h*/ #ifndef MY_H #define MY_H … #endif 在程序中包含头文件有两种格式: ##import ##import "my.h" 第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外 第 3 章 Objective-C——构建 iOS 应用程序的基石    77 部库的头文件中搜索被包含的头文件。 第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用 程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。 采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用 程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包 含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一 组公共的头文件。 2. #import 与 #include 在 Objective-C 语言中,#import 由 #include 衍生而来,不同的是它能保证一个头文件 不被多次包含。#import 被当成 #include 指令的改良版本来使用。除此之外,#import 确定一 个文件只能被导入一次,这使在处理递归包含中不会出现问题。使用哪一个还要根据实际 来决定。一般来说,在导入 Objective-C 头文件的时候使用 #import,包含 C 头文件时使用 #include。 3. #import 与 @class 在 Objective-C 语言中,可以通过声明 #import 来引用类,也可以通过声明 @class 来引 用类,例如: #import "SomeClass.h" @class SomeClass; 下面列出了二者的区别以及联系。 ‰‰ import 会包含这个类的所有信息,包括实体变量和方法 ;而 @class 只是告诉编译器, 其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再 告诉。 ‰‰ 在头文件中,一般只需要知道被引用的类的名称就可以了,不需要知道其内部的实体 变量和方法,所以在头文件中一般使用 @class 来声明这个名称是类的名称。而在实 现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用 #import 来包含这个被引用类的头文件。 ‰‰ 从编译效率方面考虑,如果有 100 个头文件都使用 #import 引用同一个头文件,或者 这些文件是依次引用的,如 A–>B、B–>C、C–>D 这样的引用关系。当最开始的那个 头文件有变化的话,后面所有引用它的类都需要重新编译,如果类有很多,将耗费大 量的时间。而使用 @class 则不会。 ‰‰ 如果有循环依赖关系(如 A–>B,B–>A 这样的相互依赖关系),使用 #import 来相互 包含就会出现编译错误,如果使用 @class 在两个类的头文件中相互声明,则不会有 编译错误出现。 一般来说,@class 是放在 interface 中的,只是为了在 interface 中引用这个类,把这个类 作为一个类型来用的。在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方 78   第二部分 语 法 篇 法之类的,还是需要包含 import 在 @class 中声明的类。 3.2 Objective-C 2.0 新增特性 在苹果公司发布的 Objective-C 2.0 中,增加了关联、快速枚举、选择器、静态方法等特 性,下面将分别详细介绍这几种特性。 3.2.1 关联引用 关联引用从 Mac OS X v10.6 版本开始生效,用于为现有的类模拟一些额外的实例变量。 使用关联引用,可以在不改动类的声明的情况下为类添加变量。当没有类的源代码,或者由 于二进制兼容性的原因不能修改对象的时候非常有用。 关联建立在一个关键字基础之上。对于任何对象都可以添加任意个数的关联,每个关联 都使用一个不同的关键字。这种关联关系可以确保被关联的对象在源对象的生命周期内保持 有效。 1. 创建关联 使用 Objective-C 运行时方法 objc_setAssociatedObject 创建两个对象之间的关联。该方 法有四个参数:源对象、关键字、关联对象、约束策略。这里需要解释关键字和约束策略。 ‰‰ 关键字是一个 void 指针。每个关联的关键字必须唯一。通常的做法是使用一个 static 变量作为关键字。 ‰‰ 约束策略用于指定被关联的对象是 assigned、retained 或者 copied,并且指定关联是自 动建立还是非自动建立。类似于为属性指定特性。 下面的代码演示了如何在一个数组和一个字符串之间建立关联。 static char overviewKey; NSArray *array =[[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil]; // For the purposes of illustration, use initWithFormat: to ensure // the string can be deallocated NSString *overview =[[NSString alloc] initWithFormat:@"%@", @"First three numbers"]; objc_setAssociatedObject ( array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN ); [overview release]; // (1) overview valid [array release]; // (2) overview invalid 注释 1,字符串 overview 仍然有效,因为 OBJC_ASSOCIATION_RETAIN 策略表明数组 会保持被关联的对象。当 array 被释放了(注释 2 位置),同时 overview 也就被释放了,这个 第 3 章 Objective-C——构建 iOS 应用程序的基石    79 时候试图去读取 overview 的值,会得到一个运行时异常。 2. 查询被关联的对象 使用运行时方法 objc_getAssociatedObject 查询一个被关联的对象。继续上面的实例,可 以使用如下代码从 array 中查询 overview。 NSString *associatedObject =(NSString *)objc_getAssociatedObject(array, &overviewKey); 3. 解除关联 想要解除关联,通常调用 objc_setAssociatedObject 方法,将参数被关联对象设为 nil。 继续上面的实例,通过如下代码解除 array 和 overview 之间的关联。 objc_setAssociatedObject(array, &overviewKey, nil,OBJC_ASSOCIATION_ASSIGN); 在这里被关联对象设为 nil,后面的约束策略其实是没有意义的。若要解除一个对象的所 有关联,可以调用 objc_removeAssociatedObjects。一般不建议使用这个方法,因为会解除所 有的关联,除非想要把一个对象恢复到原始状态。以下是前面例子的代码清单。 #import #import int main (int argc, const char * argv[]) { @autoreleasepool { static char overviewKey; NSArray *array = [[NSArray alloc]initWithObjects:@ "One", @"Two", @"Three", nil]; // For the purposes of illustration, use initWithFormat: to ensure // we get a deallocatable string NSString *overview = [[NSString alloc]initWithFormat:@"%@", @"First three numbers"]; objc_setAssociatedObject ( array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN ); [overview release]; NSString *associatedObject =(NSString *) objc_getAssociatedObject (array, &overviewKey); NSLog(@"associatedObject: %@", associatedObject); objc_setAssociatedObject ( array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN ); [array release]; } return 0; } 80   第二部分 语 法 篇 3.2.2 快速枚举 快速枚举是语言中用于快速安全地枚举一个集合的表达式,下面分别介绍快速枚举的定 义及其应用。 1. for…in 表达式 这种快速枚举表达式的定义如下: for ( Type newVariable in expression ) { statements } 或者: Type existingItem; for ( existingItem in expression ) { statements } 这两种表达式中,expression 是一个遵守 NSFastEnumeration 协议的对象。每次循环被迭 代的对象都会返回一个对象并赋给一个循环变量,同时 statements 中定义的代码被执行一次。 当被迭代的对象中已经没有数据可以取出时,循环变量被设为 nil,如果循环在这之前被停 止,那么循环变量会指向最后一次返回的值。 使用快速枚举的好处如下: ‰‰ 更有效率(和直接使用 NSEnumerator 相比)。 ‰‰ 表达式更简洁。 ‰‰ 更安全。枚举会监控枚举对象的变化,如果在枚举的过程中枚举对象发生变化会抛出 一个异常。 在循环中被循环对象是禁止修改的,所以可以同时进行多个枚举。另外,同其他循环一 样,可以使用 break 停止循环,或者使用 continue 忽略当次循环而进行到下一个元素。 2. 采用快速枚举 一个类如果想对外提供对一个数据集合的访问,可以采用 NSFastEnumeration 协议。基 础框架中的数据集合类 NSArray、NSDictionary 和 NSSet 都采用了这个协议。显而易见枚举 操作可以遍历 NSArray 和 NSSet 的内容。对于其他类,相关文档会说明哪些内容会被枚举。 例如,NSDictionary 和 NSManagedObjectModel 也支持快速枚举,但是 NSDictionary 枚举的 是它的键值,NSManagedObjectModel 枚举的是它的实体。 下面列举了枚举 NSArray 和 NSDictionary 的代码。 NSArray *array = [NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil]; for (NSString *element in array) { NSLog(@"element: %@", element); } NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: @"quattuor", @"four", @"quinque", @"five", @"sex", @"six", nil]; NSString *key; for (key in dictionary) { NSLog(@"English: %@, Latin: %@", key, [dictionary objectForKey:key]); } 第 3 章 Objective-C——构建 iOS 应用程序的基石    81 也可以使用 NSEnumerator 进行快速枚举。 NSArray *array = [NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil]; NSEnumerator *enumerator = [array reverseObjectEnumerator]; for (NSString *element in enumerator) { if ([element isEqualToString:@"three"]) { break; } } NSString *next = [enumerator nextObject]; // next = "two" 数据集合或枚举都是有序定义的,如由数组衍生出的 NSArray 或 NSEnumerator 实例的 枚举处理是按顺序进行的,所以当需要时循环计数器会提供相应数据的索引。例如: NSArray *array = <#Get an array#>; NSUInteger index = 0; for (id element in array) { NSLog(@"Element at index %u is: %@", index, element); index++; } 3.2.3 选择器 在 Objective-C 中选择符有两个含义。一种是用在代码中向对象发送消息时,代表了一 个方法名 ;另一种是源代码被编译时,选择器会指向唯一标识以代替方法名,被编译后的选 择器类型为 SEL。所有相同名字的方法会有相同的选择器,可以使用一个选择器来调用一个 对象的方法。这是 Cocoa 中目标 - 动作设计模式的基础。 1. 方法和选择器 出于运行效率的考虑,在编译后的代码中不会使用由 ASCII 码组成的方法名。编译器会 将每个方法名写到一个表中,然后为每个方法名分配一个唯一标识,用于在运行时标识一个 方法。运行时系统会确保每个标识都是唯一,不会出现两个相同的选择器,并且所有相同名 字的方法都使用同一个选择器。 编译后的选择器会分配给一个特定类型 SEL,以区别其他数据。选择器永远不会是 0, 必须让系统为方法分配 SEL 标识符,重复分配是无效的。 @selector() 指令可以直接引用编译后的选择符而不是方法名。下面例子中,将选择符 setWidth:height: 分配给变量 setWidthHeight。 SEL setWidthHeight; setWidthHeight = @selector(setWidth:height:); 在编译时使用 @selector() 指令将选择符分配给 SEL 变量是最有效率的。但是在某些情况下, 需要在运行时将一个字符串转化为一个选择器。可以是用 NSSelectorFromString 方法来实现。 setWidthHeight = NSSelectorFromString(aBuffer); 82   第二部分 语 法 篇 相反,还可以从选择器得到方法名。使用 NSStringFromSelector 方法从一个选择器返回 一个方法名。 NSString *method; method = NSStringFromSelector(setWidthHeight); 编译后的选择器标识的是方法名而不是方法本身的实现。例如,一个类有一个 display 方法,它和其他类中定义的 display 方法使用同一个选择器。这个是动态绑定和多态性的基 础 ;这样可以向不同类型的接收者发送相同的消息。如果每一个方法都有一个选择器,那么 消息机制就和方法调用完全相同了。 一个类方法和一个实例方法如果名字相同,它们的选择器也相同。但是由于它们的作用 域不同它们之间并不会造成混淆。一个类可以在定义一个 display 实例方法的同时再定义一个 display 类方法。 方法返回值类型和参数类型,消息只有通过选择器才能访问一个方法的实现,所以它对 同名方法(拥有相同的选择器)的处理都相同。它会从选择器中得到方法的参数类型和返回 值类型。因此,除非向一个静态类型的接收者发送消息,而动态绑定需要所有相同名字的方 法的返回值类型和参数类型全部相同。但是同名的类方法和实例方法可以有不同的参数类型 和返回值类型。 2. 在运行时改变消息 performSelector:、performSelector:withObject: 和 performSelector:withObject:withObject: 是在 NSObject 协议中定义的一组方法,它们都接受 SEL 类型的变量。这三个方法都可以直 接映射为消息功能。例如: [friend performSelector:@selector(gossipAbout:)withObject:aNeighbor]; 等同于: [friend gossipAbout:aNeighbor]; 这几个方法使得在运行时改变消息变为可能,同时还允许改变接收消息的对象。变量名 可以用在消息表达式的两个部分。例如: id helper = getTheReceiver(); SEL request = getTheSelector(); [helper performSelector:request]; 这个例子中,接收者 (helper) 是在运行时获得的(通过一个虚拟的方法 getTheReceiver), 同 时需要调用的方法(request)也是在运行时确定的(也是由一个虚拟的方法 getTheSelector 获得)。 注意 performSelector: 以及它相关的方法返回一个 id 类型的对象。如果调用的方法返回的值 是其他类型可以被转化为合适的类型,但是不 是所有的类型都能转化;需要方法返回的是一 个指针或者和指针类型相符合的类型。 第 3 章 Objective-C——构建 iOS 应用程序的基石    83 3. 目标 - 动作设计模式 在用户接口控制的处理中,AppKit 很好地使用了在运行时改变接收者和消息的功能。 NSControl 对象可以用于由图形设备为应用发送指令。在软件中,大部分仿真的操作设备 (如按钮、开关、把手、文本栏、刻度盘、菜单项等)处在应用与用户之间,负责将键盘、 鼠标等硬件设备所发出的指令转换为应用特有的指令。例如,“Find”按钮会将一个鼠标点击 事件转换为一个检索指令发送给应用以开始一次检索。 AppKit 定义了一个模板用于创建控制器同时也定义好了一些现成的控制器。例如, NSButtonCell 类定义了一个对象,可以把它分配给一个 NSMatrix 实例变量并为它初始化大 小、标题、图片、字体、快捷键。当用户点击一个按钮(或使用快捷键),theNSButtonCell 对象会发出一个消息指令告诉应用做什么。要做到这些,NSButtonCell 对象不仅仅要初始化 图片、大小、标题,而且要初始化消息的接收者。因此,一个 NSButtonCell 实例可以初始化 为一个动作(发送的消息中需要调用的方法)和一个目标(消息的接收者)。 [myButtonCell setAction:@selector(reapTheWind:)]; [myButtonCell setTarget:anObject]; 当用户点击相应的按钮,按钮使用 NSObject 协议中的 performSelector:withObject: 方法发 送一个消息。所有的消息都有一个参数,控制设备的实例负责发送这个消息。 如果 Objective-C 不允许消息变化,那么所有 NSButtonCell 对象只能发送相同的消息 ;所 要调用的方法名都只能固定写在 NSButtonCell 代码中。这样就不能用一个简单的机制将用户 动作转化为一个消息,因为按钮和其他控制器都需要限制消息的具体内容。 这种被限制的消息会使得一个对象不能响应多个按钮。这样就需要为每个按钮创建一个 响应的目标对象,或者目标对象需要判断是哪个按钮发送的消息并以此来决定要执行什么。 并且每次调整了用户接口都必须修改响应这些动作的方法。如果没有了动态消息会造成许多 Objective-C 所极力避免的不必要的麻烦。 4. 避免消息发送错误 如果一个对象收到一个消息调用一个它未定义的方法那么会引起一个错误。这和调用一 个不存在的方法有些相似。但是由于发送消息是在运行时进行的,所以这些错误经常只有到 程序实际运行时才会暴露出来。 当消息的选择器是一个常量并且知道接收者的类型时,这种错误相对容易避免。例如, 自己写了一个程序,当然能够确定接收者能够响应什么。如果接收者是静态类型,编译器会 替完成校验。 但是,如果消息选择器或者接收者的类型是可变的,这种校验就只能到运行时才能进 行。NSObject 类中定义的 respondsToSelector: 方法可以检验一个接收者是否可以响应一个消 息。它把方法选择器作为参数,并且返回接收者是否有一个与选择器相匹配的方法。 if ( [anObject respondsToSelector:@selector(setOrigin::)] ) 84   第二部分 语 法 篇 [anObject setOrigin:0.0 :0.0]; else fprintf(stderr, "%s can't be placed\n", [NSStringFromClass([anObject class]) UTF8String]); 当向一个对象发送消息,但是这个对象在编译时不能完全控制时,respondsToSelector: 校验就显得特别重要。例如,有的代码会根据一个变量向一个对象发送消息来运行不同的方 法,就需要确保接收者实现了需要运行的方法。 3.2.4 静态类型的使用 本节解释一些静态类型工作机制,以及 Objective-C 内部的临时停止动态处理特性。 1. 默认的动态行为 按照语言的设计,Objective-C 对象都为动态实体,尽可能将判断处理由编译时延后到运 行时,其特性如下。 ‰‰ 对象所需的内存是由创建实例的类方法在运行时动态分配的。 ‰‰ 对象是动态指定类型。在代码中(编译时),任何对象变量都可以是 id 类型不管它是 哪种类的对象。直到程序运行时才会判断这个 id 类型变量的准确类型(它的特定方 法和数据结构)。 ‰‰ 消息和方法是动态绑定的,在运行时程序将消息中的方法选择器匹配到接收者的一个 方法。 这些特性给面向对象程序提供了更多的功能和灵活性,但是这样做也是有代价的。特别 是编译器无法对 id 类型的变量进行精确的类型校验。为了在编译时能进行更好的类型校验, 同时要提高代码自身的可读性,Objective-C 允许使用类名来静态地为一个对象指定类型。同 时 Objective-C 还允许关闭一些它的面向对象特性,使一些处理从运行时回到编译时。 注意 在某种程度上,消息的方式比方法调用慢一些,但是这些性能消耗同实际执行的工 作比起来微不足道。通过取消 Objective-C 的灵活性而带来的性能变化,只有通过分析工具 (如 Shark 或 Instruments)进行分析时才能感觉到。 2. 静态类型 如果用一个类名取代 id 出现在对象声明中,如下: Rectangle *thisObject; 这样编译器会对声明的变量进行类型限制只能是声明中指定的类或其子类。如上面这个例 子,thisObject 只能是 Rectangle 或其子类类型的对象。 静态定义类型的对象与定义为 id 类型的对象拥有同样的内部数据结构。类型不会影响对 象本身,它仅仅是为编译器和代码的阅读者提供更多的信息。 第 3 章 Objective-C——构建 iOS 应用程序的基石    85 静态类型不会影响运行时对象的处理。静态类型的对象也是由创建 id 类型实例的类方 法动态分配内存。如果 Square 是一个 Rectangle 的子类,以下代码中 thisObject 仍然是一个 Square 对象拥有 Square 定义的所有实例变量,不会因为指定为 Rectangle 类型而有所不同。 Rectangle *thisObject = [[Square alloc] init]; 发送到静态类型对象的消息也是动态绑定的,与发送给 id 类型对象是相同的。静态指定 类型的接受者在运行时的消息处理中,仍然会进行精确类型的检测。例如,向 thisObject 对 象发送 display 消息: [thisObject display]; 以上执行的是 Square 类中的 display 方法,而不是其父类 Rectangle 中的方法。 作为提供给编译器额外的对象信息,静态类型比 id 类型提供了更多的可能。 ‰‰ 在某些情况下,静态类型可以进行编译时的类型校验。 ‰‰ 静态类型使对象摆脱相同名称的方法必须有相同参数类型和返回值的限制。 ‰‰ 静态类型允许使用结构指针运算符(.)直接访问一个对象的实例变量。 3. 类型校验 当使用静态类型时,编译器可以在以下两种情况下提供更好的类型校验。 ‰‰ 当向一个静态类型的接收者发送一个消息时,编译器可以确定接收者是否可以响应。 如果接收者没有与消息中方法名相对应的方法,编译器会发出一个警告。 ‰‰ 当一个静态类型的对象被分配到一个静态类型的变量,编译器会判断它们的类型是否 匹配。如果不匹配会发出一个警告。 若要在赋值操作时避免警告,需要对象的类型同变量的类型相同,或者为变量类型的子 类。下面的例子说明了这点: Shape *aShape; Rectangle *aRect; aRect = [[Rectangle alloc] init]; aShape = aRect; 这里 aRect 可以分配给 aShape,因为矩形是图形的一种(Rectangle 类继承自 Shape)。 但是,如果两者的角色调换一下,也就是说,将 aShape 分配给 aRect,编译器会生成一个警 告,因为不是所有的图形都是矩形。 当表达式中等号两边的任何一个对象的类型为 id 时,都不会进行类型校验。静态类型的 对象可以任意分配给 id 类型的对象,或者 id 类型的对象也可以分配给任何一个静态类型对 象。由于方法 alloc 和 init 会返回 id 类型的对象,所以编译器并不能确保为静态类型变量返 回一个类型合适的对象。像下面这样为变量赋值虽然很容易出错误,但是仍然是被允许的。 Rectangle *aRect; aRect = [[Shape alloc] init]; 86   第二部分 语 法 篇 4. 返回值和参数类型 通常情况下,不同类中的同名方法其参数和返回值类型也必须相同。这一限制由编译器 强制执行以实现动态绑定。因为在运行时编译器无法知道一个消息接收者的类型(实现被调 用方法的类),编译器必须对相同名字的方法相似对待。当编译器为运行时系统准备方法的 信息时,它仅创建一个方法描述来应对每一个方法选择器。 但是,当向静态类型对象发送一个消息时,编译器知道接收者的类型。编译器可以取 得关于这个特定类中方法的定义信息,因此消息的返回值和参数类型就不再受以上所述的 约束。 5. 继承类的静态类型 一个类的实例可以被静态地制定为它本身类的类型,也可以被指定为它所继承的类的类 型。例如,所有的实例都可以被静态地指定为 NSObject 类型。但是,编译器只是通过类型 定义中的类名来确定类的静态类型并进行类型校验。因此为一个实例指定一个它所继承类的 类型,会使编译器所预期的处理与运行时实际的处理出现偏差。 例如,把一个 Rectangle 实例指定为 Shape 类型,编译器会认为它就是一个 Shape 实例。 Shape *myRectangle = [[Rectangle alloc] init]; 如果向这个对象发送一个消息运行一个 Rectangle 中的方法,编译器会报错。 BOOL solid = [myRectangle isFilled]; 因为 isFilled 方法是在 Rectangle 类中定义的(而不是在 Shape 中)。但是,如果发送一个消 息运行一个 Shape 中的方法,编译器不会报错: [myRectangle display]; 即使 Rectangle 已经复写了这个方法,但是在运行时,实际运行的方法是 Rectangle 中定义的 方法。类似地,有一个 Upper 类声明一个 worry 方法有一个 double 类型返回值: -(double)worry; 同时,Upper 的子类 Middle 复写了这个方法并返回一个新类型的返回值: -(int)worry; 如果一个实例被静态指定为 Upper 类型,编译器会认为它的 worry 方法返回一个 double 类型返回值,同时如果一个实例被指定为 Middle 类型,编译器会认为 worry 应该返回一个 int 类型。如果一个 Middle 实例被指定为 Upper 类型,编译器会通知运行时系统对象的 worry 方 法返回一个 double 类型,但是在运行时实际的返回值为 int 类型同时会生成一个错误。 静态类型可以使同名方法不必受必须使用相同类型参数返回值的约束,但是只有应用在 不同继承关系中定义的方法才可靠。 第 3 章 Objective-C——构建 iOS 应用程序的基石    87 3.3 进一步认识块 在前面的介绍中,我们已经初步了解了块,本节将进一步介绍块的用法及其特性。块 是添加到 C、Objective-C 和 C++ 中的语言特征,允许创建不同的代码段作为值传递给方 法或函数。同时,块还是 Objective-C 的对象,这意味着可以把它们添加到集合中,例如 NSArray 或 NSDictionary。因为它们还能从封闭的范围内获取值,使它类似于 Closures 或者 Lambdasin 等编程语言。 3.3.1 块可以带参数和返回值 就像方法和函数一样,块也可带参数并且返回值。下面的例子中,块带有两个参数,并 且返回 double 类型的值,代码如下: double (^multiplyTwoValues)(double, double); 相应的块文字也可以看起来像这样: ^ (double firstValue, double secondValue) { return firstValue * secondValue; } firstValue 和 secondValu 作为块的输入参数,就像带有参数的函数定义一样。在这个例子 中,返回值的类型由块内部返回语句决定。 当然,也可以在外部明确返回值的类型,方法是在符号“^”和左括号“(”之间插入指 定的返回值类型,例如: ^ double (double firstValue, double secondValue) { return firstValue * secondValue; } 一旦声明和定义了块,就可以调用它,就像使用函数一样: double (^multiplyTwoValues)(double, double) =^(double firstValue, double secondValue) { return firstValue * secondValue; }; double result = multiplyTwoValues(2,4); NSLog(@"The result is %f", result); 3.3.2 块可以捕获封闭范围内的值 对于包含的可执行代码,块也能从包含它的范围内捕获不同的状态。如果在一个方法中 声明块文字,它可以捕获任何访问该方法范围内的值,例如: - (void)testMethod { int anInteger = 42; void (^testBlock)(void) = ^{ 88   第二部分 语 法 篇 NSLog(@"Integer is: %i", anInteger); }; testBlock(); } 在这个例子中,变量 anInteger 在块外被声明且初始化,但块可以在定义的区域内捕获 anInteger 的值。这意味着,如果改变的值在块定义之后,改变变量 anInteger 的值不会影响捕 获值的变化,例如: int anInteger = 42; void (^testBlock)(void) = ^{ NSLog(@"Integer is: %i", anInteger); }; anInteger = 84; testBlock(); 由块捕获的值不受影响。这意味着,日志输出仍然是: Integer is: 42 这说明该块不能改变原来的变量,以及捕获的值。 3.3.3 在块内捕获变量值的变化 如果使用 _block 存储类型修饰符来修改原来的变量声明,这意味着,变量一直存活在 共享存储,即在原始变量的词法作用域和任何块的声明区域之间,也可以捕获到该变量的变 化。举个例子,重写前面的例子: _block int anInteger = 42; void (^testBlock)(void) = ^{ NSLog(@"Integer is: %i", anInteger); }; anInteger = 84; testBlock(); 因为 anInteger 被声明为 _block 是共享的存储块变量,现在的日志输出显示: Integer is: 84 这说明该块可以修改原始的值: _block int anInteger = 42; void (^testBlock)(void) = ^{ NSLog(@"Integer is: %i", anInteger); anInteger = 100; }; testBlock(); NSLog(@"Value of original variable is now: %i", anInteger); 这一次输出会显示: 第 3 章 Objective-C——构建 iOS 应用程序的基石    89 Integer is: 42 Value of original variable is now: 100 3.3.4 块可以作为函数或者方法的参数 在前面的例子中,块定义之后会立即调用它。在实际的开发中常见的用法是,把块传递 给函数或者方法用于其他地方的调用。在后台,可以使用“Grand Central Dispatch”调用块。 例如定义一个块供一个任务重复性调用(后面会讨论块的并发性和枚举)。 块也可以用来进行回调,定义要执行的代码作为要完成的任务。程序可能通过创建一个对 象执行复杂的任务,来响应用户的操作,例如一个 Web 服务的请求信息。由于执行任务可能会 花费很长的时间,当某种任务发生时,最好能显示出各种进度;一旦任务完成立刻隐藏该指示。 块也可以用来进行回调,定义要执行的代码作为要完成的任务。程序可能通过创建一个 对象来执行复杂的任务来响应用户的操作,例如一个 Web 服务的请求信息。由于执行任务可 能会花费很长的时间,当某种任务发生时,最好能显示出各种进度,一旦任务完成立刻隐藏 该指示。将使用委托,即创建一个合适的委托协议,实现所需要的方法,把你的对象设置为 任务的委托,然后等待,一旦任务完成立即调用对象中的委托方法。 使用块可以使这些变得容易很多,但是在任务初始化开始的时候,就能定义回调的行 为,例如: - (IBAction)fetchRemoteInformation:(id)sender { [self showProgressIndicator]; XYZWebTask *task = ... [task beginTaskWithCallbackBlock:^{ [self hideProgressIndicator]; }]; } 编写代码时,在任务完成的前后,通过块让它聚集在一个地方来参与处理任务,可以大 大增加代码的可读性。下面的例子中,声明 beginTaskWithCallbackBlock 方法。 -(void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock; 在块的应用中,可以用参数 (void (^)(void)) 作为块不带任何值的参数或者返回值。下面 执行的方法就是以通常的方式调用块: - (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock { ... callbackBlock(); } 块也可以是方法的参数,以一个或多个参数的方式作为块变量: - (void)doSomethingWithBlock:(void (^)(double, double))block { ... block(21.0, 2.0); } 90   第二部分 语 法 篇 3.3.5 用类型定义可以简化块语法 如果需要定义多个块具有相同签名的,可能想定义自己的签名类型。举个例子,可以定 义一个类型,不带任何参数或返回值,这样一个简单的块如下所示: typedef void (^XYZSimpleBlock)(void); 然后,使用自定义类型作为方法的参数或者用来创建块变量,例如: XYZSimpleBlock anotherBlock = ^{ ... }; - (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock { ... callbackBlock(); } 把块作为返回值或者把其他的块作为参数时,自定义类型特别有用。看下面的例子: void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) { ... return ^{ ... }; }; 变量 complexBlock 指向的块把另一个块作为参数,并且把另一个块作为返回值。使用 类型定义重写代码,这样变得更可读: XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) { ... return ^{ ... }; }; 3.3.6 使用属性可跟踪块 定义一个属性来跟踪一个块,跟定义一个块变量类似,例如: @interface XYZObject : NSObject @property (copy) void (^blockProperty)(void); @end 注意 由于在原来的范围之外,需要用一个块来跟踪其捕获的状态,所以最好特别复制一下 作为属性的特性。在使用自动的引用计数时,不用担心,因为它会自动发生,且对于属性的 特性其最佳的处理方式是能显示其行为的结果。 第 3 章 Objective-C——构建 iOS 应用程序的基石    91 块属性设置或调用就像任何其他块变量一样: self.blockProperty = ^{ ... }; self.blockProperty(); 也可以使用类型定义块属性的声明,例如: typedef void (^XYZSimpleBlock)(void); @interface XYZObject : NSObject @property (copy) XYZSimpleBlock blockProperty; @end 3.4 小结 通过本章介绍,相信你对于Objective-C 语言整体上有了一个清晰的认识。同时对 于 Objective-C 2.0 语言新增的几个特性以及 iOS 4 新增的块也有更全面的认识和理解。在 后面的章节中,我们将围绕Objective-C 的某一个点做进一步介绍,一步一步地加深对 Objective-C 语言的掌握。
还剩109页未读

继续阅读

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

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

需要 5 金币 [ 分享pdf获得金币 ] 29 人已下载

下载pdf