Objective-C 2.0 教程


Objective-C 2.0 with Cocoa Foundation 文章来自 : http://www.cnblogs.com/yaski/ 前言 1,前言 相信 iPhone 不久就要 在国内发布了 ,和我们在国 内可以通过正 规渠道买得到 的 iPod Touch 一样 ,iPhone 也是一个 激动人心的产 品 。iPhone 发布的同 时 ,基于 iPhone 的程序也 像雨后春笋 一样在 iTunes 里面冒出 来。 你将来也 许会考虑买一 个 iPhone,体验一 下苹果的富有 创意的种种应 用;你也许会 考虑 向 iTunes 的社 区的全 世界的 人们展 示一下 你非凡 的创意 ,当然 也可以 通过你 的创意 得到一 些意想 不到的收 益。 OK,你也许迫 不及待的准备 开发了 。但是先等 一下 ,让我们回 忆一下最初的 电影是怎么拍 摄的。这 个很重要,因 为和 iPhone 的开发比 较类似。 在最初 因为器材比 较原始,所 以拍摄电影 需要很高的 技术,那个 时候的电影 的导演基本 上 是可以 熟练操作摄 影器材的人 。随着器材 的完善,使 用也简单起 来。于是器 材的使用不 是决定 一个电影 的质量的唯一 的因素,取而 代之的是故事 或者说电影的 创意。 iPhone 的开发 也是这样。 当然从入门 到掌握的过 程来说任何 事情都是开 始比较难, 随着掌 握的程度 的加深 ,你将会觉 得开发 iPhone 应用程序 是一件简单而 且轻松的事情 ,到了那个 时候 , 你的主 要的制胜武 器就不是开 发技术,而 是你的创意 了。对于你 来说,我在 这里写的东 西都是 有关 “摄影器材 ”也就是介 绍如何使用 iPhone 的平台来 开发应用程序 。 iPhone 的开发语言是Objective-C。Objective-C 是进行iPhone 开发的主要语言,掌握了 Objective-C 的 基本 语法 以 及数 据结 构之 后 ,你 需要 熟悉 一 下 iPhone 的SDK。 笔者 很难 做 到在 一篇文章 里面把所有的 东西都介绍清 楚,所以笔者 打算分成两个 主题,一个是 Objective-C,一 个是 iPhone 开发。 本系列将 侧重于 Objective-C。当然 ,任何一种 开发语言都无 法脱离于运行 环境 ,Objective-C 也不例外 。所以在本 系列当中也会 穿插的介绍一 些 SDK 里面的一 些特性 ,主要是数 据结构方面 , 比如说NSString, NSArray 等 等 。看 到 NSString, NSArray 这 些 名词 , 你也 许 会 感到 有 些茫 然 , 不过没有 关系,随着本 系列的深入介 绍,你会发现 你非常喜欢这 些东西。 1.1,谁会考虑 阅读本系列 如果你对 iPhone 感兴趣 ,如果你考 虑向全世界的 人们展示你的 创意 ,如果你有 一颗好奇心 , 如果你打 算通过开发 iPhone 程序谋生 ,如果你觉 得苹果比 Windows 酷,如果你认 为不懂苹果的 话那么就 有些不时尚的 话,那么可以 考虑阅读本系 列。 老手也 可以考虑花 一点时间阅 读一下,可 以发帖子和 笔者交流切 磋。笔者发 布的文章属 于 公益写作 ,旨在为大 家介绍 iPhone 开发的一 些基础知识 ,如果可以 提供宝贵意见 ,笔者将不 胜 感激。 1.2,需要准备 的东西 公欲善其 事,必先利其 器。 《论语 ·魏灵公》 第一 ,你需 要一台 苹果电 脑。当 然这个 不是必 需的条 件,如 果你可 以在你 的 Intel PC 上成 功安装 MACOS的话,那 么请忽略这一 条。 第二 ,你需要去 苹果网站上下 载开发工具 XCODE。注意 ,XCODE 是完全免 费的 ,但是需 要你去注 册一个账号才 可以下载 。由于 XCODE 不时的在 更新 ,所以如果 你的 MAC OS 不支持 你下载的 XCODE 的话,那 么你也许需要 考虑买一个最 新的 MACOS。 第三, 你需要至少 有 C,C++,或者 JAVA的背景 知识。不过 如果你没有 ,那么也不 用担心, 相信阅读 了笔者的文章 之后应该也可 以掌握。 最后需 要的东西就 不是必须的 了,当然有 的话会更好 一些。这些 东西是,开 发者账户( 需 要付费 ),iPhone 手机 (在部分国 家可以免费获 得 ,但是中国 会怎么样 ,笔者不是 很清楚 ),iPod Touch(需要购 买 )。 1.3 ,关于笔者 的写作 笔者利 用业余时间 进行写作, 所以无法对 文章发布的 时间表做出 任何保证, 还请各位读 者 谅解。但 是笔者会尽最 大努力在短时 间之内完成写 作。 对于已 经完成的东 西,基于一 些条件的改 变或者勘误 ,或者大家 提出的意见 ,笔者也会 考 虑做出适 当的修改。 1.4,本系列的 结构 第1章,也就 是本章 第2章, 从Hello,World!开始 第3章, 类的声明 和定义 第4章,继承 第5章, Class 类型,选 择器 Selector 以及函数 指针 第6章, NSObject 的奥秘 第7章,对象 的初始化以及 实例变量的作 用域 第8章,字符 串,数组以及 字典 第9章,内存 管理 第10 章,到目 前为止出现的 内存泄漏事件 第11 章,属性 第12 章,类目 (Categories) 第13 章,协议 (Protocols) 第14 章, Delegate 第15 章,线程 第16 章,文件 系统 第17 章,数据 系列化以及保 存用户数据 第18 章,网络 编程 第19 章, XML解析 上面带 连接的章节 是已经完成 的章节,否 则就是还没 有发布的。 发布过的讲 座的内容可 能 会更新, 甚至本系列讲 座的结构会发 生改变。 Objective-C 2.0 with Cocoa Foundation --- 2 从Hello,World!开始 现在笔者 假设大家已经 有了开发的环 境。好了,我 们开始构筑我 们的第一个程 序。 在开始 第一个程序 之前,笔者 需要提醒大 家一下,如 果手里面有 开发环境的 话并且是第 一 次亲密接 触 Xcode 的话,为 了可以熟悉开 发环境,强烈 建议按照笔者 的步骤一步一 步的操作下 去。 2.1,构筑 Hello, World 第一步 ,启动 Xcode。初次启动 的时候 ,也许会弹 出一个 “Welcome to Xcode”的一个对 话框 。 这个对话 框和我们的主 题没有关系, 我们可以把它 关掉。 第二步, 选择屏幕上部 菜单的 “File->New Project”,出现了一 个让你选择项 目种类的对话 框 。 你需要在 对话框的左边 选择 “Command Line Utility”,然后在右 边选择 “Foundation Tool”,然后选 择“Choose...”按钮。如 图 2.1 所示。 图2-1,新建项 目 注意 也许有人会问,你不是要讲解iPhone 的开发,那么为什么不选择“iPhone OS”下面的 “Application”呢? 是这样的 ,在这个系 列当中 ,笔者主要 侧重于 Objective-C 的语法的 讲解 ,为了使得 讲解简 单易懂, 清除掉所有和 要讲解的内容 无关的东西, 所以笔者在这 里只是使用最 简单的命令行 。 第三步,Xcode 会提问你项目的名字,在“Save As”里面输入“02-Hello World”,然后选择 “Save”。如图 2-2 所示 图片看不 清楚?请点击 这里查看原图 (大图 )。 图2-2,输入项 目的名字 第四 步,得 到一个 如图 2-3 所示 的一个 画面。 尝试一 下用鼠 标分别 点击左 侧窗口 栏里面 的 “02-Hello World”,“Source”.“Documentation”,“External Frameworks and Libraries”,“Products”, 然后观察 一下右边的窗 口都出现了什 么东西 。一般来说 ,“02-Hello World”就是项目 的名字下面 是项目 所有的文件 的列表。项 目下面的子 目录分别是 和这个项目 相关的一些 虚拟或者实 际上的 目录。为 什么我说是虚 拟的呢?大家 可以通过 Finder 打开你的 工程文件的目 录,你会发现 你的 所有文件 居然都在根目 录下,根本就 不存在什么 “Source”之类的目 录。 图片看不 清楚?请点击 这里查看原图 (大图 )。 图2-3,项目浏 览窗口 第五步 ,选择屏幕 上方菜单的 “Run”然后选择 “Console”,出现了如 图 2-4 所示的画 面 ,用鼠 标点击窗 口中间的 “Build and Go”按钮。 图片看不 清楚?请点击 这里查看原图 (大图 )。 图2-4,运行结 果画面 如 果不 出什 么意 外 的话 ,大 家应 该 看到 我们 熟悉 得 不能 再熟 悉的 “Hello Wolrd!”。 由于 我 们没有 写任何的代 码,所以从 理论上来说 ,这部分代 码不应该出 现编译错误 。好的,从 下面开 始,笔者 要开始对这个 Hello World 里面的一 些新鲜的东西 进行讲解。 2.2,头文件 导入 在Java 或者 C/C++里面 ,当我们的 程序需要引用 外部的类或者 方法的时候 ,需要在程 序源 文 件 中 包 含外 部 的 类 以 及 方法 的 包 ( java 里面的jar package) 或 者 头 文件 ( C/C++的.h),在 Objective-C 里面也有 相类似的机制 。笔者在这一 节里面将要向 大家介绍在 Objective-C 里面 ,头 文件是怎 样被包含进来 的。 请同学们 到 Xcode 开发环境 的左侧窗口里 面 ,点击 Source 文件夹 ,然后就在 右侧部分看到 了代 码源文 件的列 表,找 到 02-Hello World.m 之后 单击会 在 Xcode 的窗 口里面 出现, 双击鼠 标 代码会在 一个新窗口出 现,请同学们 按照这种方法 打开 "02-Hello World.m"。 对于 Java 程序来说 ,源程序的 后缀为 .java,对于 C/C++代码来说 ,后缀为 c/cpp,现在我们 遇 到了 .m。当Xcode 看 到了 .m 文 件之 后, 就会 把这 个文 件当 作 Objective-C 文 件来 编译 。同 学 们也许会 猜到,当 Xcode 遇到 c/cpp,或者 java 的时候也 会对应到相应 的语言的。 好的 ,我们顺便 提了一下 Xcode 对.m 文件的约 定 ,现在我们 开始从第一行 代码讲起 ,请参 看下列代 码: 1 #import 2 3 int main (int argc, const char * argv[]) { 4 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 5 6 // insert code here 7 NSLog(@"Hello, World!"); 8 [pool drain]; 9 return 0;10 } 11 有过 C/C++经验的同 学看到第一行 ,也许会觉 得有些亲切 ;有过 Java 经验的同 学看到第一 行也许 也会有一种 似曾相识的 感觉。同学 们也许猜到 了这是干什 么用的,没 错,这个正 是头文 件。不过 ,在 C/C++里面是 #include,在 java 里面是 import,这里是 #import。 在C/C++里面会有#include 互 相 包 含的 问 题 , 这 个 时候 需 要 #ifdef 来 进 行 编译 的 导 向 , 在 Xcode 里面 ,同学们可 以 "放心的 "包含各种 东西 ,这个没有 关系 ,因为我们 的编译器有足 够的 “聪 明”,因为同一 个头文件只是 被导入一次 。除了 #import 变得聪明 了一点之外 ,和#include 的功能 是完全一 样的。 我们再来看看我们的另外一个新的朋友---Foundation.h。这个是系统框架Foundation framework 的头文件 ,有了它你 可以免费的获 取系统或者说 苹果公司为你 精心准备的一 系列方便 你 使用 的系 统功 能 ,比 如说 字符 串 操作 等等 。 Foundation 框 架从 属于 Cocoa 框 架集 , Cocoa 的 另外一 个框架为 Application Kit,或者 是 UIKit,其中 前者的应用 对象为 MACOS,后者 的应用 对象为iPhone OS。本系列入门指南将只是使用Foundation,因为笔者需要向同学们介绍 Objective-C 的基本使 用方法,为了 避免过多的新 鲜东西给同学 们造成阅读上 的困难,所以 命令 行就已经 足够了。 说 到这 里, 笔者 需 要澄 清一 点, 其 实 MACOS的Cocoa 和iPhone 的Cocoa 是 不一 样的 ,可 以说,其 中 iPhone 是MAC OS 的一个子 集。 2.3,main 函数 有过 C/C++或者 java 经验的同 学们对第 3行代码应 该很熟悉了 ,是的大家 都一样主程序 的 入口都是 main。这个 main 和C/C++语言里面 的 main 是完全一 样的 ,和java 语言在本 质上也是 完全一样 的。因为 Objective-C 完全的继 承了 C语言的特 性。确切的说 ,不是说 Objective-C 和 C语言很相 似,而是 Objective-C 和C语言是完 全兼容的。 关于main 函 数是 干什 么用 的, 笔者 就不 在这 里罗 嗦了 ,有 兴趣 的同 学可 以找 一本 C语言 的书看看 。 2.4,关于 NSAutoreleasePool 自己动手 ,丰衣足食 --- 在第 4行,我们 遇到了另外一 个新鲜的东西 ,这就是 NSAutoreleasePool。 让我把这 个单词分为三 部分, NS,Autorelease 和Pool。 当我们看 到 NS的时候 ,也许不知 道是代表着什 么东西 。NS其实只是 一个前缀 ,为了避免 命名上的冲突。NS来自于NeXTStep 的一个软件,NeXT Software 的缩写,NeXT Software 是 Cocoa 的前身, 一开始使用的 是 NS,为了保 持兼容性所以 NS一直得以 保留。在多人 开发的时 候,为 了避免命名 上的冲突, 开发组的成 员最好事先 定义好各自 的前缀。但 是,最好不 要有同 学使用 NS前缀,这 样会让其他人 产生误解。 略微有些 遗憾的是 ,Objective-C 不支持 namespace 关键字 ,不知道后 续的版本是否 会支持 。 下面我们 讨论一下 Autorelease 和Pool。 程序在 执行的时候 ,需要向系 统申请内存 空间的,当 内存空间不 再被使用的 时候,毫无 疑 问内存 需要被释放 ,否则有限 的内存空间 会很快被占 用光光,后 面的程序将 无法得到执 行的有 效内存 空间。从计 算机技术诞 生以来,无 数的程序员 ,我们的无 数先辈都在 为管理内存 进行努 力的工作 ,发展到现在 ,管理内存的 工作已经得到 了非常大的完 善。 在Objective-C 或者说 Cocoa 里面,有 三种内存的管 理方式。 第一 种, 叫做 “Garbage Collection”。这 种方 式和 java 类似 ,在 你的 程序的 执行 过程 中,始 终有一 个高人在背 后准确地帮 你收拾垃圾 ,你不用考 虑它什么时 候开始工作 ,怎样工作 。你只 需要明 白,我申请 了一段内存 空间,当我 不再使用从 而这段内存 成为垃圾的 时候,我就 彻底的 把它忘 记掉,反正 那个高人会 帮我收拾垃 圾。遗憾的 是,那个高 人需要消耗 一定的资源 ,在携 带设 备里面 ,资源 是紧俏 商品所 以 iPhone 不支 持这个 功能。 所以 “Garbage Collection”不是 本入 门 指南 的范 围, 对 “Garbage Collection”内 部机 制感 兴趣 的 同学 可以 参考 一 些其 他的 资料 , 不过 说老实话 “Garbage Collection”不大适合 适初学者研究 。 第二种 ,叫做 “Reference Counted”。就是说 ,从一段内 存被申请之后 ,就存在一 个变量用于 保存这 段内存被使 用的次数, 我们暂时把 它称为计数 器,当计数 器变为 0的时候 ,那么就是 释 放这段内 存的时候 。比如说 ,当在程序 A里面一段 内存被成功申 请完成之后 ,那么这个 计数器 就从 0变成 1(我们把这 个过程叫做 alloc),然后程序 B也需要使 用这个内存 ,那么计数 器就 从 1变成 了 2(我 们把这 个过程 叫做 retain)。紧 接着程 序 A不再 需要这 段内存 了,那 么程序 A就 把这个计 数器减 1(我们把这 个过程叫做 release);程序 B也不再需 要这段内存的 时候 ,那么也 把计数器 减 1(这个过 程还是 release)。当系统 (也就是 Foundation)发现这个 计数器变成了 0,那 么就会调 用内存回收程 序把这段内存 回收 (我们把这 个过程叫做 dealloc)。顺便提一 句 ,如果没 有Foundation,那么维 护计数器,释 放内存等等工 作需要你手工 来完成。 这样做 ,有一个明 显的好处就是 ,当我们不 知道是 A先不使用 这段内存 ,还是 B先不使用 这段内存 的时候 ,我们也可 以非常简单的 控制内存 。否则 ,当我们在 程序 A里面释放 内存的时 候,还需要看 看程序 B是否还在 使用这段内存 ,否则我们 在程序 A里面释放 了内存之后 ,可怜 的程序 B将无法使 用这段内存了 。这种方式 ,尤其是在 多线程的程序 里面很重要 ,如果多个 线 程同时使 用某一段内存 的时候,安全 的控制这些内 存成为很多天 才的程序员的 梦魇。 如果有同 学搞过 COM 的话 ,那么应该 对 Release/AddRef 很熟悉了 ,其实 Obejctive-C 和他们 的机制是 一样的。 接下来 ,我需要 解释一下 Autorelease 方式 。上述的 alloc->retain->release->dealloc 过程看 起 来比较令人满意,但是有的时候不是很方便,我们代码看起来会比较罗嗦,这个时候就需要 Autorelease。Autorelease 的 意思 是, 不是 立即 把计 数器 减 1而 是把 这个 过程 放在 线程 里面 加以 维护 。当线程开 始的时候 ,需要通知 线程 (NSAutoreleasePool),线程结束 之后 ,才把这段 内存 释放 (drain)。Cocoa 把这个维 护所有申请的 内存的计数器 的集合叫做 pool,当不再需 要 pool(水 池)的时候就 要 drain(放水 )。 笔 者 想要 说 的是 , 虽 然 iPhone 支持Autorelease 但 是 我们 最 好不 要 使 用。 因 为 Autorelease 方式从 本质上来说 是一种延迟 释放内存的 机制,手机 的空间容量 有限,我们 必须节约内 存,确 定不需 要的内存应 该赶快释放 掉,否则当 你的程序使 用很多内存 的情况下也 许会发生溢 出。这 一 个习 惯最 好 从刚 刚开 始学 习 使用 Objective-C 的 时候 就养 成 ,否 则长 时间 使 用 Autorelease 会 让你 变得 “懒散 ”,万 一遇到 问题的 时候, 解决起 来会非 常耗费 时间的 。所以 ,还是 关于内 存管 理,我们 还是自己动手 ,丰衣足食。 当然笔者不是 说绝对不可以 使用,而是当 使用 Autorelease 可以明显 减低程序复杂 度和易读性的 时候,还是考 虑使用一下换 一下口味。 说到这 里,可能有 的同学已经 开始发晕了 ,认为这个 东西比较难 以理解。是 的,笔者在 这 里只是 介绍一个大 概的东西, 在这里只要 了解计数器 的概念就可 以了,笔者 将在随后的 章节里 面对这个 功能加以详细 论述,请同学 们放心,这个 东西和 Hello World 一样简单 。 关于 Pool 在 使用 Pool 的 时候 ,也 要记 住系 统给 你的 Pool 的 容量 不是 无限 大的 ,从 这一 点来 说和 在 现实世界 的信用卡比较 相似。 你可以在 一定程度透支 ,但是如果 “忘记掉 ”信用卡的 额度的话,会 造成很大的系 统风险。 第三种 ,就是传统 而又原始的 C语言的方 式 ,笔者就不 在这里叙述了 。除非你在 Objective-C 里面使用 C代码,否 则不要使用 C的方式来 申请和释放内 存,这样会增 加程序的复杂 度。 线程是 什么东西? 线程指的是 进程中一个 单一顺序的 控制流。它 是系统独立 调度和分派 的 基本单 位。同一进 程中的多个 线程将共享 该进程中的 全部系统资 源,比如文 件描述符和 信号处 理等等。 一个进程 可以有很多线 程,每个线程 并行执行不同 的任务。 2.5,关于 [[NSAutoreleasePool alloc] init]; 关于程序 第 4行等号右 边出现的括弧 以及括弧里面 的内容 ,笔者将在 后续的章节里 面介绍 。 在这里 ,同学们 可以理解为 ,通过告 诉 Objective-C 编译器 [[NSAutoreleasePool alloc] init],编译 器就会成 功的编译生成 NSAutoreleasePoo 对象的代 码就可以了。 2.6,Objective-C 里面的注 释 同学 们在第 6行看 到了 //的注 释,这 个和 C++以及 Java 是一 样的, 表示这 一行的 内容是 注 释 ,编 译器 将会 忽略 这一 行的 内容 。笔 者在 上面 说过 Objective-C 完 全兼 容 C语 言, 所以 C语 言里面传 统的 /**/在Objective-C 里面也是 有效的。 2.7,命令行 输出 第7行,我们看到 了 NSLog 这个函数 。NS上面已经 讲过了 ,我们都知 道 Log 是什么意 思 , 那么这段代码的意思就是输出一个字符串,Xcode 的代码生成器自己把字符串定义为“Hello, World!”。NSLog 相当于 C语言里 面的 printf,由于我 们是在使用 Objective-C 所以笔 者将会和同 学们一起 ,在这里暂时 忘记掉我们过 去曾经熟悉的 printf。 有眼光锐 利的同学会发 现在字符串的 前面多了一个 @符号,这 是一个什么东 西呢? 如 前所 述, Objective-C 和C是 完全 兼容 的 ,但 是 NSLog 这 个函 数需 要 的参 数是 NSString, 这样就产 生了一个问题 ,如果使用 C的字符串 方式的话 ,为了保持 和 C的兼容性 编译器将会把 字符串理解为C的字符串。为了和C的字符串划清界限,在C的字符串前面加上@符号, Objective-C 的编译器 会认为这是一 个 NSString,是一个 NSLog 喜欢的参 数。 为什么 NSLog 或者 Cocoa 喜欢使 用 NSString?因为 NSString 封装了 一系列的字 符串的方 法比如字 符串比较,字 符串和数字相 互转换等等的 方法,使用起 来要比 C的字符串 方便的多 。 2.8,本章总 结 非常感谢 同学们耐心的 看到这里! 通过理解 本章的内容 ,同学们应 该可以使用 Xcode 创建一个 命令行的工程 ,理解 .m 文件的 基本要素 ,理解内存 的管理方法的 思路 ,还有 Objective-C 的注释的 写法 ,以及命令 行的输出方 法。 是不是 很简单又很 有乐趣呢? 笔者将会尽 最大努力把 看起来复杂 的东西讲解 的简单一些 , 并且真心 的希望大家可 以从中找到乐 趣。 下一章我 们要讲解一个 同学们已经很 熟悉的一个概 念, Class 也就是类 。 系列文章 : Objective-C 2.0 with Cocoa Foundation--- 1,前言 Objective-C 2.0 with Cocoa Foundation --- 3,类的声明 和定义 Objective-C 2.0 with Cocoa Foundation--- 4,继承 Objective-C 2.0 with Cocoa Foundation--- 5,Class 类型,选 择器 Selector 以及函数 指针 Objective-C 2.0 with Cocoa Foundation--- 6,NSObject 的奥秘 Objective-C 2.0 with Cocoa Foundation--- 7,对象的 初始化以及实 例变量的作用 域 Objective-C 2.0 with Cocoa Foundation --- 3 类的声明 和定义 上一 章我 们写 了一个 非常 简单 的 Obejctive-C下面 的 Hello, World!的小 程序 ,并 且对里 面出 现的一些 新的概念进行 了解释 。这一章 ,我们将要 深入到 Objective-C 的一个基 本的要素 ,也就 是类的 声明和定义 。通过本章 的学习,同 学们应该可 以定义类, 给类加上变 量,还有通 过方法 访问类的 变量。不过准 确的说,变量 和方法的名词 在 Objective-C 里面并不 是最准确的称 呼 ,我 们暂时引 用 Java 的定义, 稍后我们将统 一我们的用语 定义。 3.1,本章的程 序的执行结果 。 我们 将构筑 一个类 ,类的 名字叫 做 Cattle,也就 是牛的 意思, 今年是 牛年而 且我还 想给在 股 市奋战的 同学们一个好 的名字,所以 我们暂时把这 个类叫做牛类 。 我们在 main 里面初始 化这个牛类 ,然后调用 这个类的方法 设定类的变量 ,最后调用 这个类 的一个方 法,在屏幕上 输出,最终输 出的结果如下 图 3-1 所示 图片看不 清楚?请点击 这里查看原图 (大图 )。 图3-1,牛类的 输出结果 不过为了 熟悉编辑环境 以及代码,笔 者强烈建议同 学们按照下面 的步骤自己输 入。 3.2,实现步 骤 第一步, 按照我们在第 二章所述的方 法,新建一个 项目,项目的 名字叫做 03-Hello Class。 当然 ,你也可以 起一个别的更 好听的名字 ,比如说 Hello Cattle 等等 ,这个并不 妨碍我们的讲 解 。 如果你是 第一次看本系 列文章,请到 这里参看第二 章的内容。 第二步 ,把鼠标移 动到左侧的窗 口的 “Source”目录 ,然后单击 鼠标右键 ,选择 “Add”,然后 界面上会 出来一个子菜 单,在子菜单 里面选择 “New File...”。如图 3-2 所示: 图片看不 清楚?请点击 这里查看原图 (大图 )。 图3-2,新建文 件 第三步,在新建文件对话框的左侧选择“Cocoa Touch Classes”,然后在右侧窗口选择 “NSObject subclass”,然后单 击 “Next”。如图 3-3 所示: 图片看不 清楚?请点击 这里查看原图 (大图 )。 第四步 ,在“New File”对话框里 面的 “File Name”栏内输入 “Cattle.m”。注意 ,在确省状 态下 , Xcode 为你加上 了 “.m”的后缀 ,这个也是 编译器识别 Objective-C 源文件的 方法 ,没有特殊 理由 请不要 修改这个后 缀,否则会 让编译器感 到不舒服。 另外请确认 文件名字输 入栏的下方 有一个 “Also create "Cattel.h"”选择框, 请保持这个选 择框为选择的 状态。如图 3-4 所示。 第5步,在项 目浏览器里面 选择 “Cattle.h”文件,把 文件改为如下 代码并且保存 : #import @interface Cattle : NSObject { int legsCount;} -(void)saySomething; -(void)setLegsCount:(int) count; @end 为什么 legs Cattle 者,牛也 ;legs 者,股也 。不过牛股 里面的牛正确 的英文说法应 该是 Bull,请大家不 要着急, 我们会在类的 继承里面命名 一个 Bull 类的。 第六步, 在项目浏览器 里面选择 “Cattle.m”文件,把 文件改为如下 代码并且保存 : #import "Cattle.h" @implementation Cattle-(void) saySomething{ NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount);} -(void) setLegsCount:(int) count{ legsCount = count; } @end 第七步, 在项目浏览器 里面选择 “03-Hello Class.m” 文件,把 文件改为如下 代码并且保存 : #import #import "Cattle.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle = [Cattle new]; [cattle setLegsCount:4]; [cattle saySomething]; [pool drain]; return 0; } 第八步, 选择屏幕上方 菜单里面的 “Run”,然后选 择 “Console”,打开了 Console 对话框之 后 , 选择对话 框上部中央的 “Build and Go”,如果不 出什么意外的 话,那么应该 出现入图 3-1 所示的 结果。 如果出现了 什么意外导 致错误的话 ,那么请仔 细检查一下 你的代码。 如果经过仔 细检查 发现还是 不能执行的话 ,可以下载 笔者为同学们 准备的代码 。如果笔者 的代码还是不 能执行的 话,请告 知笔者。 3.3,类的声 明 从Objective-C 名字我们 就可以得知 ,这是一个 面向对象的语 言 。面向对象 的一个最基础 的 要素就是 类的概念 ,Objective-C 也不例外 。所谓的类 的概念 ,其实是从 C语言的结 构体发展而 来的 。我们 知道, C语言 里面的 结构体 仅仅有 数据的 概念, 面向对 象的语 言不仅 仅支持 数据, 还可以 在结构体里 面封装用于 存取结构体 数据的方法 。结构体的 数据和方法 结合,我们 把整个 结构 体称为 类 (Class)。仅 仅有了 类,是 不能执 行任何 操作的 ,我们 必须把 类进行 实体化 ,实体 化后的类 我们称之为对 象 (Object)。从这个 角度上来说, 我们可以认为 类是对象的模 版。 如果要使 用类,那么和 构造体相类似 ,我们必须声 明这个类。 请参照 “Cattle.h” 文件: 1 #import 2 3 4 @interface Cattle : NSObject { 5 int legsCount; 6 } 7 -(void)saySomething; 8 -(void)setLegsCount:(int) count;9 @end 如果看 过本系列第 二章的同学 们,第一行 应该是一个 老面孔了, 我们知道我 们需要这个 东 西免 费获得 苹果公 司为我 们精心 准备的 Foundation Framework 里面 的很多 的功能 。如果 不使用 这个东西 的话,我们的 工作将会很复 杂。 同学 们请看 第 4行和 第 9行的 第一个 字母, 又出现 了 “@”符号 。为什 么说又 呢,因 为我们 在 第二章的 字符串前面也 看到过这个东 西 。字符串前 面出现这个符 号是因为我们 需要和 C语言的 字符 串定义 区别开 来,我 们需要 编译器 导向。 在这里 ,我要 告诉同 学们的 是,这 里的 “@”符号 的作用还 是同样是编译 器导向 。我们知道 Java 和C++定义了一 个关键字 class 用于声明 一个类 , 在Objective-C 里面 ,不存在 这样的关键 字 。在Objective-C 里面 ,类的定 义从 @interface 开始到 @end 结 束, 也就 是说 , 编译 器看 到了 @interface 就 知道 了这 是类 的 定义 的开 始, 看 到了 @end 就知道, 类的定义结束 了。 我们 这里类 的名字 是 “Cattle”,我 们使用 了空格 和 @interface 分开 ,通知 编译器 ,我们 要声 明一 个类, 名字叫 做 Cattle。在 Cattle 的后 面,我 们有 “: NSObject”,这 是在通 知编译 器我们 的 Cattle 是从 NSObject 继承而来 的,关于继承 和 NSObject,我们将 在后面的章节 里面详细介绍 , 关于 “: NSObject”我们现在 可以理解为 ,通过这样 写 ,我们免费 获得了苹果公 司为我们精心 准备 的一系 列的类和对 象的必备的 方法 。NSObject 被称为 root class,也就是 根类 。在Java 或者 .NET 里面 ,根类是必 备的 ,C++不需要 。在Obejctive-C 里面原则 上 ,你可以不 使用 NSObject,构筑 一个你 自己的根类 ,但是事实 上这样做将 会有很大工 作量,而且 这样做没有 什么意义, 因为苹 果为你提 供的 NSObject 经过了很 长时间的检验 。也许有好 奇心的同学们 想自己构筑根 类 ,不过 至少笔者 不会有自己去 构筑一个根类 的欲望。 好的, 大家现在来 看第 5行。我 们以前把这 个东西叫做 变量,我们 从现在开始 ,需要精确 的使用 Objective-C 的用语了 ,这是实体 变量 (instance variables,在有的英 文资料里面会 简写 为 iVars)。虽然作 为一个 Cattle,它有不 止一个实体变 量,比如说体 重等等,但是 为了代码简洁 , 我们在这 里声明一个就 是牛腿也就是 牛股的数目 ,这个实体 变量是 int 型,表示一个 整数 ,我们 当然不希 望有 4.5 个牛腿。 我们来 看第 6行,第 6行的括 弧和在第 4行最后 的括弧用来 表示实体变 量的定义区 间,编译器 认为在 这两个括弧 之间的定义 是实体变量 的定义。当 然,如果你 的类没有实 体变量,那 么这两 个括弧之 间允许什么都 没有 。和Java 以及 C++不一样 ,Objective-C 要求在括 弧里面不能有 方法 也就是函 数的定义,那 么 Objective-C 里面的方 法的定义放在 什么地方呢, 请看第 7行。 第7行的第一 个字母是一个 减号 “-”。这个减号 就是告诉编译 器 ,减号后面 的方法 ,是实体 方法 (instance method)。实体方法 的意思就是说 ,这个方法 在类没有被实 体化之前 ,是不能运 行 的。我们在这 里看到的是减 号 ,在有减号 的同时也有加 号 ,我们把带 加号的方法称 为类方法 (class method),和实体方 法相对应 ,类方法可 以脱离实体而 运行 。关于类方 法 ,我们将在 后面的章节 里面讲解 。大家也许可 以想起来在 C++和Java 里面同样 也有类似的区 分,不是么。 在Objective-C 里面方法 的返回类型需 要用圆括号包 住 ,当编译器 看到减号或者 加号后面的 括号了之 后,就会认为 这是在声明方 法的返回值。 你也可以不声 明返回值, Objective-C 的编译 器会给没 有写显式的返 回值函数加上 一个默认的返 回值 ,它的类型 是 id,关于 id 类型我们 将在 后面讲解 ,不过笔者不 推荐不写返回 值的类型。 在第 7行我 们定 义了 这个方 法的 名字 是 saySomething,当 然 Cattle 说的 话我 们人 类是听 不 懂的, 笔者只是想 让它在我们 的控制台里 面输出一些 我们可以看 得懂得字符 串。方法的 声明最 后,需要 分号来标识, 这一点保持了 和 C没有任何 区别。 我们再来 看看第 8行,第 8行和第 7行多了 “:(int) count”。其中冒 号放在方法的 后面是用 来表示 后面是用来 定义变量的 ,同样变量 的类型使用 括号给包住 ,如果不写 变量的类型 的化, 编译 器同 样认 为这是 一个 id 类型 的。 最后 的 count,就 是变 量的 名字。 如果 有不 只一个 变量 怎 么办?答 案就是在第一 个变量后面加 冒号 ,然后加园 括号包住变量 的类型 ,接着是变 量的名字 。 好了,我 们在这里总结 一下,类的定 义方法如下: @interface 类的名字: 父类的名字{ 实 体 变 量 类型 实 体 变 量 名字 ;}- (返 回值 类型 )方 法名 字 ;+ (返 回值 类型 )方 法名 字 ;-(返 回值 类型 )方 法名 字 :(变 量类 型 ) 变量 名字 :(变量类型 ) 变量名字 ;@end ...的意思 在本系列 入门讲座里面 , ...表示省略 了一些代码的 意思。 3.4,类的定 义 我们在前 一节讲述了类 的声明 ,我们下一 步将要看一下 类的定义 。请同学们 打开 “Cattle.m” 文件: 1 #import "Cattle.h" 2 3 4 @implementation Cattle 5 -(void) saySomething 6 { 7 NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount); 8 } 9 -(void) setLegsCount:(int) count 10 { 11 legsCount = count; 12 } 13 @end 14 Cattle.m 文件的 第一行就 import 了Cattle.h 文件 ,这一点 和 C的机制 是一样的 ,关于 #import 的说明请 参照第二章。 我们来看 第 4行和第 13 行,和头 文件里面的 @一样,我 们这里类的定 义也是使用的 编译导向 。 编译器会 把从 @implementation 到@end 之间的部 分看作是类的 定义 。@implementation 的后面有 一个空格 ,空格的后面 是我们的类的 名字 Cattle,这是在 告诉编译器, 我们要定义 Cattle 类了 。 第4行和第 13 行之间是 我们在头文件 里面定义的实 体方法或者类 方法的定义部 分 ,当然我们 的 类如 果没 有任 何的实 体方 法和 类方法 的话 ,我 们也许 要写 上 @implementation 和@end,把 中间 留为空就 可以了。 第5行 是我 们 定 义的 saySomething 的 实现 , 我 们可 以 发现 第 5行 的内 容 和 头文 件 Cattle.h 的第 7行是一 致的。笔者 个人认为在 编写实体方 法和类方法 的定义的时 候,为了避 免手工输入 产生的 误差,可以 从头文件当 中把声明的 部分拷贝过 来,然后删 除掉分号, 加上两个花 括弧。 我们知道 地 6行到第 8行是方法 的定义的部分 ,我们再来 看看第 7行。第7行和第二 章的 Hello, World 输出有些 相似 ,只不过多 了一个 %d,还有实体 变量 legsCount,这个写法 和 C语言里面 的 printf 是类似的 ,输出的时候 会使用 legsCount 来替代字 符串里面的 %d。 第9行的内容 和 Cattle.h 的第 8行一致的 ,这个不需要 再解释了。我 们来看看第 11 行, 第 11 行是在说 ,把参数 count 的数值赋 值给实体变量 legsCount。我们可 以通过使用 setLegsCount 方法来控 制 Cattle 对象里面 legsCount 的数值。 这部分内 容的关键点为 @implementation 和@end,理解了这 个东西 ,其余的就 不难理解了 。 我们来总 结一下,类的 定义部分的语 法: @implementation 类的名字 -(方法返回 值 ) 方法名字 { 方法定义 } -(方法返回 值 ) 方法名字 :(变量类型 ) 变量名字 { 方法定义 } @end 3.5,类的实例 化 我们在 3.3 和3.4 节里面分 别声明和定义 了一个 Cattle 的类 。虽然定义 好的类 ,但是我们 是 不能直接 使用这个类的 。因为类的 内容需要被调 入到内存当中 我们称之为内 存分配 (Allocation), 然后需要 把实体变量进 行初始化 (Initialization),当这些步 骤都结束了之 后 ,我们的类 就被实例 化了 ,我们把实 例化完成的类 叫做对象 (Object)。好的 ,我们知道 了我们在类的 实例化过程当 中 需要做哪 些工作,我们 接着来看看我 们已经搞定的 Cattle 类的定义 和声明是怎样 被实例化的。 1 #import 2 #import "Cattle.h" 3 4 int main (int argc, const char * argv[]) { 5 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 6 7 id cattle = [Cattle new]; 8 [cattle setLegsCount:4]; 9 [cattle saySomething]; 10 11 [pool drain]; 12 return 0; 13 } 同 学们 请看 第 7行 的第 一个 单词 id。id 是 英文 identifier 的 缩写 ,我 们在 很多 地方 都遇 到 过id,比如说在 博客园里面 ,我们都使 用 id 来登陆系 统的 ,我们的 id 就代表着 系统的一个用 户 。 由于 id 在一个系 统当中是唯一 的 ,所以系统 获得我们的 id 之后就知 道我们是谁了 。Objective-C 也是 一样的 道理, 使用 id 来代 表一个 对象, 在 Objective-C 当中 ,所有 的对象 都可以 使用 id 来 进行区分 。我们知道 一个类仅仅是 一些数据外加 上操作这些数 据的代码 ,所以 id 实际上是 指向 数据结构 的一个指针而 已,相当于 void*。 第7行 的第 二个 单词 是 cattle, 就是 我们 给这 个 id 起 的一 个名 字。 当然 ,你 可以 起系 统保 留 的名字 以外的任何 名字,不过 为了维持代 码的可读性 ,我们需要 一个有意义 的名字,我 们这里 使用头文 字为小写的 cattle。 Objective-C 里 面的 方法 的使 用 和其 他语 言有 些 不同 , Objective-C 使 用消 息( Message)来 调用方 法。所以笔 者认为在讲 解第 7行等号 右边的部分 之前,需要 首先向大家 介绍一个我 们的 新朋友 ,消息 (Message)。所谓的消 息就是一个类 或者对象可以 执行的动作 。消息的格 式如下 : [对象或者 类名字 方法名字 :参数序列 ]; 首先我们 观察到有两个 中括弧 ,最右边的 括弧之后是一 个分号 ,当编译器 遇到了这个格 式 之后会 把中间的部 分当作一个 消息来发送 。在上文的 表达式当中 ,包括中括 弧的所有部 分的内 容被称作 消息表达式 (Message expression),“对象或者 类名字 ”被称作接 收器 (Receiver),也就 是 消 息 的接 受 者 , “方法名字:参数序列”被 称 为 一个 消 息 (Message),“方法名字”被 称 作 选择 器 (Selector)或者关 键字 (Keyword)。Objective-C 和C语言是 完全兼容的 ,C语言里 面的中括弧 用于 表示数 组,但是数 组的格式明 显和消息的 发送的格式 是不一样的 ,所以我们 可以放心, 编译器 不会把我 们的消息发送 当作一个数组 。 我们来回 忆一下 C语言里面 函数的调用过 程 ,实际上编 译器在编译的 时候就已经把 函数相 对于整 个执行包的 入口地址给 确定好了, 函数的执行 实际上就是 直接从这个 地址开始执 行的。 Objective-C 使 用的 是一 种间 接的 方式 , Objective-C 向 对象 或者 类( 具体 上是 对象 还是 类的 名 字取决于 方法是实体方 法还是类方法 )发送消息, 消息的格式应 该和方法相同 。具体来说, 第 7行等号右 边的部分 [Cattle new]就是说 ,向Cattle 类发送一 个 new 的消息 。这样当 Cattle 类接 收到 new 的时候 ,就会查找 它可以相应的 消息的列表 ,找到了 new 之后就会 调用 new 的这个类 方法,分 配内存和初始 化完成之后返 回一个 id,这样我 们就得到一个 对象。 Objective-C 在编译的 过程当中 ,编译器是 会去检查方法 是否有效的 ,如果无效 会给你一个警 告 。 但是编 译器并不会 阻止你执行 ,因为只有 在执行的时 候才会触发 消息,编译 器是无法预 测到执 行的时 候会发生什 么奇妙的事 情的。使用 这样的机制 给程序毫无 疑问将给带 来极大的灵 活性, 因为我 们和任意的 对对象或者 类发送消息 ,只要我们 可以保证执 行的时候类 可以准确地 找到消 息并且执 行就可以了, 当然如果找不 到的话,运行 会出错。 任何事物 都是一分为二 的 --- 任何事物都是一分为二的,在我们得到了灵活性的时候我们损失的是执行的时间。 Objective-C 的这种方 式要比直接从 函数的入口地 址执行的方式 要消耗更多的 执行时间,虽 然编 译器对寻 找的过程作过 一定的优化。 有 的同 学会 觉得 奇怪 ,我 们在 Cattle 里 面并 没有 定义 new, 我们 可以 向 Cattle 发 送这 个类 方法 么?答 案是可 以,因 为 new 在NSObject 里面 ,实际 上响应 new 消息 的是 NSObject。实 际 上new 类似于 一个宏,并 不是一个 “原子 ”的不可 再分的方法 ,关于详细 的情况,我 们将在后续 的章节里 面讲解。 有 了第 7行 的讲 解, 那么 第 8行 的内 容就 不难 理解 了, 第 8行 实际 上是 想 cattle 对 象发 送 一个setLegsCount 的 消息 ,参 数是 4, 参照 Catttle.m, 我们 可以 发现 这 个时 候我 们希 望 实体 变 量legsCount 是4。第 8行就更 简单了,就 是说向 cattle 对象发 送一个 saySomething 的消息 ,从 而实现了 控制台的输出 。 3.6,本章总 结 通过本章 的学习,同学 们应该掌握如 下概念 如何声明 一个类 如何定义 一个类 实体变量 的定义 类方法和 实体方法的定 义 id 是什么 NSObject 的奇妙作 用 如何从类 开始初始化对 象 消息的调 用 感谢大家 看到这里!我 们下一章将要 讲述继承的概 念。 系列文章 : Objective-C 2.0 with Cocoa Foundation--- 1,前言 Objective-C 2.0 with Cocoa Foundation --- 2,从Hello,World!开始 Objective-C 2.0 with Cocoa Foundation--- 4,继承 Objective-C 2.0 with Cocoa Foundation--- 5,Class 类型,选 择器 Selector 以及函数 指针 Objective-C 2.0 with Cocoa Foundation--- 6,NSObject 的奥秘 Objective-C 2.0 with Cocoa Foundation--- 7,对象的 初始化以及实 例变量的作用 域 Objective-C 2.0 with Cocoa Foundation--- 4 继承 上一章笔 者介绍了一下 在 Objective-C 里面的类 的基本构造和 定义以及声明 的方法 。我们知道 在 面向对 象的程序里 面,有一个 很重要的需 求就是代码 的重复使用 ,代码的重 复使用的重 要方法 之一就 是继承。我 们在这一章 里面,将要 仔细的分析 一下继承的 概念以及使 用的方法。 有过其 他面向对 象语言的同学 ,对这一章的 内容应该不会 感到陌生。 4.1,本章的程 序的执行结果 在 本章 里面 ,我 们将 要重 复使 用第 3章 的部 分代 码。 我们 在第 3章 构筑 了一 个叫 做 Cattle 的类 ,我 们在 这一章 里面 需要 使用 Cattle 类, 然后 基于 Cattle 类, 我们 需要 构筑一 个子 类, 叫 做Bull 类。Bull 类 里面 ,我 们追 加 了一 个实 例变 量 ,名 字叫 做 skinColor, 我们 也将 要追 加 2 个 实例 方法 ,分 别 getSkinColor 还有setSkinColor。 我们 然后 需要 更改 一下 我们 的 main 函 数, 然后在 main 函数里面 让我们的 Bull 做一下重 要讲话。第 4章程序的 执行结果如图 4-1 所示: 图片看不 清楚?请点击 这里查看原图 (大图 )。 图4-1,本章程 序的执行结果 4.2,实现步 骤 第一步,按照我们在第二章所述的方法,新建一个项目,项目的名字叫做04-Hello Inheritance。如果你 是第一次看本 篇文章,请到 这里参看第二 章的内容。 第二步 ,把鼠标移 动到项目浏览 器上面的 “Source”上面 ,然后在弹 出的菜单上面 选择 “Add”, 然后在子 菜单里面选择 “Exsiting Files”,如图 4-2 所示 图片看不 清楚?请点击 这里查看原图 (大图 )。 图4-2,向项目 追加文件 第三 步,在 文件选 择菜单 里面, 选择第 3章的 项目文 件夹 “03-Hello Class”,打 开这个 文件 夹之后 ,用鼠标 和苹果电脑 的 COMMAND 键,选泽文 件 “Cattle.h”和“Cattle.m”,然后按 下 “Add” 按钮,如 图 4-3 所示。 图片看不 清楚?请点击 这里查看原图 (大图 )。 图4-3,选择文 件 第四步,在追加文件的选项对话框里面,让“Copy items into destination group's folder(if needed)” 的单选框 变为被选择的 状态。这样就 保证了我们在 第三步里面选 择的文件被拷 贝到了 本章的项 目里面 ,可以避免 我们不小心更 改 “Cattle.h”和“Cattle.m”对已经生 效的第 3章程序产 生 影响,虽 然我们在本章 里面不更改这 2个代码。 第五步 ,把鼠标移 动到项目浏览 器上面的 “Source”上面 ,然后在弹 出的菜单上面 选择 “Add”, 然后在子 菜单里面选择 “New Files”,然后在 新建文件对话 框的左侧选择 “Cocoa Touch Classes”, 然 后在 右侧 窗口 选择 “NSObject subclass”, 选择 “Next”,在“New File”对 话框 里面 的 “File Name” 栏内输入 “Bull.m”。在这里笔 者没有给出图 例 ,在这里新 建文件的步骤 和第 3章的第二 步到第四 步相同, 只是文件名字 不一样。第一 次看到本篇文 章的同学可以 参照第 3章。 第六步, 打开 Bull.h 做出如下 修改,并且保 存。 #import #import "Cattle.h"@interface Bull : Cattle { NSString *skinColor; } -(void)saySomething; -(NSString*) getSkinColor; -(void) setSkinColor:(NSString *) color; @end 第七步, 打开 Bull.m 做出如下 修改,并且保 存 #import "Bull.h"@implementation Bull -(void) saySomething{ NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); } -(NSString*) getSkinColor{ return skinColor; } -(void) setSkinColor:(NSString *) color{ skinColor = color; } @end 第八步, 打开 04-Hello Inheritance.m 文件,做 出如下修改, 并且保存 #import #import "Cattle.h" #import "Bull.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle = [Cattle new]; [cattle setLegsCount:4]; [cattle saySomething]; id redBull = [Bull new]; [redBull setLegsCount:4]; [redBull setSkinColor:@"red"]; [redBull saySomething]; Bull *blackBull = [Bull new]; [blackBull setLegsCount:4]; [blackBull setSkinColor:@"black"]; [blackBull saySomething]; [pool drain]; return 0; } 第九步, 选择屏幕上方 菜单里面的 “Run”,然后选 择 “Console”,打开了 Console 对话框之 后 , 选择对话 框上部中央的 “Build and Go”,如果不 出什么意外的 话,那么应该 出现入图 4-1 所示的 结果。 如果出现了 什么意外导 致错误的话 ,那么请仔 细检查一下 你的代码。 如果经过仔 细检查 发现 还是不 能执行的话 ,可以下载 笔者为同学 们准备的代 码。 如果笔 者的代码还 是不能执行 的话,请 告知笔者。 4.3,子类 Subclass 和超类 Superclass 让我们首 先回忆一下第 3章的 Cattle.h,在 Cattle.h 里面我们 有如下的代码 片断: @interface Cattle : NSObject { 这段代码 是在告诉编译 器,我们的 Cattle 是继承的 NSObject。在这段 代码当中, NSObject 是超类 ,Cattle 是子类 。通过这样 写 ,我们曾经 免费的得到了 NSObject 里面的一 个方法叫做 new。 id cattle = [Cattle new]; 在面向 对象的程序 设计当中, 如果在子类 当中继承了 超类的话, 那么超类当 中已经生效 的 部分代 码在子类当 中仍然是有 效的,这样 就大大的提 高了代码的 效率。基于 超类我们可 以把我 们需 要追加 的一些 功能放 到子类 里面去 ,在本 章里面 ,我们 决定基 于 Cattle 类, 重新生 成一个 子类 Bull: 1 #import 2 #import "Cattle.h" 3 4 @interface Bull : Cattle { 5 NSString *skinColor; 6 } 7 -(void)saySomething; 8 -(NSString*) getSkinColor; 9 -(void) setSkinColor:(NSString *) color;10 @end 上段代码 里面的第 2行,是通知编 译器 ,我们这个 类的声明部分 需要 Cattle.h 文件 。这个文件 我 们已经很 熟悉了 ,是我们在 第 3章曾经构 筑过的 ,在本章里 面 ,我们不会 改变里面的任 何内容 。 第4行,就是在通 知编译器 ,我们需要 声明一个类名 字叫做 Bull,从Cattle 里面继承 过来 。 第5行,我们 追加了一个实 例变量 skinColor,用来保 存 Bull 的颜色。 第7行,我们重载了在Cattle 类里面已经有的(void)saySomething 实例方法。重载 (void)saySomething 方法的主 要原因是,我 们认为 Bull 说的话应 该和 Cattle 有所区别 。 第8行到第9行,我们为Bull 类声明了两个新的方法(NSString*) getSkinColor 和(void) setSkinColor:(NSString *) color,分别用 来设定和读取 我们的实例变 量 skinColor。 好的,我 们总结一下继 承的时候的子 类的格式。 @interface 类的名字: 父类的名字{ 实 体 变 量 类型 实 体 变 量 名字 ;}- (返 回值 类型 )重 载的 方法 名 字 ;+ (返 回值 类型 )重 载的 方法 名 字 ;-(返 回值 类型 )其 他的 方法 名 字:(变量类型 ) 变量名字 :(变量类型 ) 变量名字 ;@end 4.4,self 和super 我们再来 打开 “Bull.m”,在saySomething 的定义的 部分,我们发 现了如下的代 码: NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); 我们在这 句话当中 ,发现的第 一个新朋友是 %@,这是在告 诉编译器 ,需要把 %@用一个后 面定义的 字符串来替换 ,在这里我们 给编译器提供 的字符串是 [self getSkinColor]。看到这 里 , 同学们又 会发现一个新 的朋友 self。 在类的 方法定义域 之内,我们 有的时候需 要访问这个 类自己的实 例变量,或 者是方法。 在类 被实例化 之后 ,我们就可 以使用一个指 向这个类本身 的一个指针 ,在Java 或者 C++里面的名 字 叫做this,在Objective-C 里 面, 这个 名字 是 self。self 本 身是 一个 id 类 型的 一个 指针 变量 。我 们在第 3章里面讲 解过,方法的 调用格式如下 : [对象或者 类名字 方法名字 :参数序列 ]; 在类的 方法定义域 里面,当我 们需要调用 类的其他方 法的时候, 我们需要指 定对象或者 类 的名字, 我们的方法是 一个实例方法 所以我们需要 一个指向自己 的对象,在这 里我们需要使 用 self。 我们假 设,如果方 法声明里面 的参数序列 里面有一个 参数的名字 和类的实例 变量发生重 复 的情况 下并且由于 某种原因我 们无法更改 参数和实体 变量的名字 的话,我们 应该如何应 对呢? 答案是使 用 self,格式如 下 self->变量名字 通过这样 写 ,我们可以 取得到类的变 量的数值 。当然如果 没有名字冲突 的话 ,我们完全 可 以省略 self->,Xcode 也足够的 聪明能够识别 我们的实例变 量 ,并且把我 们代码里面的 实例变量 更改为相 应的醒目的颜 色。 第1行的代码 在第 3章里面讲 解过,我们来 看看第 2行的代码 。 第2行的代码 实际上是向 redBull 发送一个 setLegsCount 消息 ,参数为 4。我们没有 在 Bull 里面定义 setLegsCount 方法,但 是从控制台的 输出上来看, setLegsCount 明显是得 到了执行 。 在执行 的时候,我 们给 redBull 发送 setLegsCount 消息的 时候, runtime 会在 Bull 的映射 表当中 寻找setLegsCount, 由于 我们 没有 定 义所 以 runtime 找 不到 的。 runtime 没 有找 到指 定的 方 法的 话,会接着需要Bull 的超类,也就是Cattle。值得庆幸的是,runtime 在Cattle 里面找到了 setLegsCount,所以就 被执行了。由 于 runtime 已经寻找 到了目标的方 法并且已经执 行了,所以 它 就 停 止了 寻 找 。 我 们 假设 runtime 在Cattle 里 面 也 没有 找 到 , 那 么 它会 接 着 在 Cattle 的超类 NSObject 里面 寻找 ,如 果还是 找不 到的 话,由 于 NSOBject 是根 类, 所以 它会报 错的 。关 于具 体内部是 一个怎样的机 制,我们将在 后面的章节里 面讲解。 第3行的代码 ,是设定 skinColor。 第4行的代 码是给 redBull 发送 saySomething 的消息 。按照第 2行的 runtime 的寻找 逻辑 , 它 首先 会在 Bull 类 里面 寻找 saySomething, 这一 次 runtime 很 幸运 ,它 一次 就找 到了 ,所 以就 立即执行 。同时 runtime 也停止了 寻找的过程, 所以, Cattle 的saySomething 不会得到 执行的 。 在第 6行里 面, 我们 定义了 一个 blackBull,但 是这 一次 我们没 有使 用 id 作为 blackBull 的 类型,我 们使用了 Bull *。从本质 上来说,使用 id 还是 Bull *是没有任 何区别的。但 是 ,我 们来想象 ,当我们的 程序存在很多 id 类型的变 量的话 ,我们也许 就难以区分究 竟是什么类型 的 变量了 。所以,在 没有特殊的 理由的情况 之下,我们 最好还是显 式的写清楚 类的名字, 这样可 以方便其 他人阅读。由 于 Bull 从Cattle 继承而来 ,我们也可以 把地 6行代码改 为 Cattle *blackBull = [Bull new]; 如 果我 们 在 类的 方 法里 面 需 要访 问 超类 的 方 法或 者 变量 (当 然是 访 问 对子 类 来说 是 可 视的 方 法或者变 量 ),我们需 要怎样写呢? 答案是使用 super,super 在本质上 也是 id 的指针, 所以 ,使 用super 访问变量 和方法的时候 的书写格式, 和 self 是完全一 样的。 “Bull.m”里面的其 他的代码,没 有什么新鲜的 东西,所以笔 者就不在这里 赘述了。 4.5,超类方 法和子类方法 的执行 我们来看 一下 04-Hello Inheritance.m 的下面的 代码片断 1 id redBull = [Bull new]; 2 [redBull setLegsCount:4]; 3 [redBull setSkinColor:@"red"]; 4 [redBull saySomething]; 5 6 Bull *blackBull = [Bull new]; 7 [blackBull setLegsCount:4]; 8 [blackBull setSkinColor:@"black"]; 9 [blackBull saySomething]; 4.6,本章总 结 感谢大家 阅读到这里! 我们在本章学 习了: 超类,子 类的概念以及 如何定义和声 明。 self 和super 的使用方 法以及使用的 时机。 超类和子 类的方法的执 行。 我们在下 一章将要讲述 selector 等等的一 些其他的重要 的概念。 系列文章 : Objective-C 2.0 with Cocoa Foundation--- 1,前言 Objective-C 2.0 with Cocoa Foundation --- 2,从Hello,World!开始 Objective-C 2.0 with Cocoa Foundation --- 3,类的声明 和定义 Objective-C 2.0 with Cocoa Foundation--- 5,Class 类型,选 择器 Selector 以及函数 指针 Objective-C 2.0 with Cocoa Foundation--- 6,NSObject 的奥秘 Objective-C 2.0 with Cocoa Foundation--- 7,对象的 初始化以及实 例变量的作用 域 Objective-C 2.0 with Cocoa Foundation--- 5,Class 类型,选 择器 Selector 以及函数 指针 上一章笔 者介绍了在 Objective-C 里面继承 的概念 。有了继承 的知识我们可 以重复的使用 很多 以前生 效的代码, 这样就大大 的提高了代 码开发的效 率。在本章 ,笔者要向 同学们介绍 几个非 常重要的 概念, Class 类型, 选择器 Selector 以及指针 函数。 我们在 实际上的编 程过程中, 也许会遇到 这样的场景 ,那就是我 们在写程序 的时候不能 确 切的知 道我们需要 使用什么类 ,使用这个 类的什么方 法。在这个 时候,我们 需要在我们 的程序 里面动 态的根据用 户的输入来 创建我们在 写程序不知 道的类的对 象,并且调 用这个对象 的实例 方法 。Objective-C 为我们提 供了 Class 类型 ,选择器 Selector 以及指针 函数来实现这 样的需求 , 从而大大 的提高了我们 程序的动态性 能。 在Objective-C 里面 ,一个类被 正确的编译过 后 ,在这个编 译成功的类里 面 ,存在一个 变量 用于保存 这个类的信息 。我们可以 通过一个普通 的字符串取得 这个 Class,也可以通 过我们生成 的对象取 得这个 Class。Class 被成功取 得之后 ,我们可以 把这个 Class 当作一个 已经定义好的 类 来使用它 。 Selector 和Class 比 较类 似, 不同 的地 方是 Selector 用 于表 示方 法。 在Objective-C 的 程序 进行编 译的时候, 会根据方法 的名字(包 括参数列表 )确定一个 唯一的身份 证明(实际 上就是 一个整数 ),不用的类 里面的相同名 字相同声明的 方法的身份证 明是一样的 。这样在程 序执行的 时候 ,runtime 就不用费 力的进行方法 的名字比较来 确定是执行哪 一个方法了 ,只是通过 一个整 数的寻 找就可以马 上定位到相 应的方法, 然后找到相 应的方法的 入口地址, 这样方法就 可以被 执行了。 笔者在前 面的章节里面 叙述过 ,在Objective-C 里面消息 也就是方法的 执行比 C语言的直 接 找到函数 入口地址执行 的方式,从效 率上来讲是比 较低下的。尽 管 Objective-C 使用了 Selector 等招数来 提高寻找效率 ,但是无论 如何寻找的过 程 ,都是要消 耗一定的时间 的 。好在 Objective-C 是完全兼 容 C的,它也有指 针函数的概念 。当我们需 要执行效率的 时候 ,比如说在 一个很大的 循环当 中需要执行 某个功能的 时候,我们 可以放弃向 对某一个对 象发送消息 的手段,用 指针函 数取而代 之,这样就可 以获得和 C语言一样 的执行效率了 。 说到这里,可能有的同学已经有些茫然了。这些概念有些令人难以理解,但是它们确实是 Objective-C 的核心的 功能 。掌握了这 些核心的功能 之后 ,同学们可 以很轻松的看 懂苹果的 SDK 里面的 很多东西含 义,甚至可 以自己动手 写一些苹果 没有为我们 提供的功能 。所以建议 大家仔 细研读本 章的内容,如 果有什么问题 ,可以发个帖 子大家可以共 同探讨。 从笔者的 观点上来看, 对于有 Java 或者 C++或者其他 面向对象的语 言的经验的同 学来说 , 前面的从第1到第4章的内容也许有些平淡无奇。从第5章开始,我们将要逐渐的深入到 Objective-C 的核心部 分 。笔者的最 终目的 ,虽然是向 大家介绍 iPhone 开发的入 门 ,但是笔者 认 为 了解 了 Objective-C 的 基本 概念 以及 使 用方 法之 后, 熟 悉 iPhone 的 应用 程序 的开 发 将是 一件 水到渠成 的轻松的事情 。否则如果 你直接就深入 到 iPhone 的开发的 话 ,在绝大多 数时间你也许 因为一个小小的问题就会困扰你几个小时甚至几天,解决这些问题的唯一方法就是熟悉 Objective-C 和Cocoa Foundation 的特性。 好了, 说了很多我 们从下面就 要开始,我 们的手法和 前面几章是 一样的,我 们首先要介 绍 一下本章 程序的执行结 果。 5.1,本章程 序的执行结果 图片看不 清楚?请点击 这里查看原图 (大图 )。 图5-1,第 5章程序的 执行结果 在本 章里面 ,我们 将要继 续使用 我们在 前面几 章已经 构筑好 的类 Cattle 和Bull。为 了灵活 的使用 Cattle 和Bull,我们将 要构筑一个 新的类 ,DoProxy。在DoProxy 里面 ,我们将 会引入几 个我们的 新朋友 ,他们分别 是 BOOL,SEL,IMP,CLASS。通过这些 新的朋友我们 可以动态的 通过设定 文件取得 Cattle 和Bull 的类,还 有方法以及方 法指针。下面 将要介绍如何 构筑本章程 序。同 学们可以按 照本章所述 的步骤来构 筑,也可以 通过下载。 不过为了熟 悉代码的写 作,笔 者强烈建 议大家按照笔 者所述的步骤 来操作。 5.2,实现步 骤 第一步 ,按照我们 在第 2章所述的 方法 ,新建一个 项目 ,项目的名 字叫做 05-Hello Selector。 如果你是 第一次看本篇 文章,请到这 里参看第二章 的内容。 第二 步, 按照 我们在 第 4章的 4.2 节的 第二 ,三 ,四步 所述 的方 法,把 在第 4章已 经使 用 过的 “Cattle.h”,“Cattle.m”,“Bull.h”还有 “Bull.m” 导入本章 的项目里面。 第三步 ,把鼠标移 动到项目浏览 器上面的 “Source”上面 ,然后在弹 出的菜单上面 选择 “Add”, 然后在子 菜单里面选择 “New Files”,然后在 新建文件对话 框的左侧选择 “Cocoa Touch Classes”, 然 后在 右侧 窗口 选择 “NSObject subclass”, 选择 “Next”,在“New File”对 话框 里面 的 “File Name” 栏内输入 “DoProxy.m”。在这里笔 者没有给出图 例 ,在这里新 建文件的步骤 和第 3章的第二 步到 第四步相 同,只是文件 名字不一样。 第一次看到本 篇 文章的同 学可以参照第 3章。 第四步, 打开 “DoProxy.h”做出如下 修改并且保存 #import #define SET_SKIN_COLOR @"setSkinColor:" #define BULL_CLASS @"Bull"#define CATTLE_CLASS @"Cattle"@interface DoProxy : NSObject { BOOL notFirstRun; id cattle[3]; SEL say; SEL skin; void(*setSkinColor_Func) (id, SEL, NSString*); IMP say_Func; Class bullClass; } -(void) doWithCattleId:(id) aCattle colorParam:(NSString*) color; -(void) setAllIVars; -(void) SELFuncs; -(void) functionPointers;@end 第五步, 打开 “DoProxy.m”做出如下 修改并且保存 #import "DoProxy.h" #import "Cattle.h"#import "Bull.h" @implementation DoProxy -(void) setAllIVars{ cattle[0] = [Cattle new]; bullClass = NSClassFromString(BULL_CLASS); cattle[1] = [bullClass new]; cattle[2] = [bullClass new]; say = @selector(saySomething); skin = NSSelectorFromString(SET_SKIN_COLOR);} -(void) SELFuncs{ [self doWithCattleId:cattle[0] colorParam:@"brown"]; [self doWithCattleId:cattle[1] colorParam:@"red"]; [self doWithCattleId:cattle[2] colorParam:@"black"]; [self doWithCattleId:self colorParam:@"haha"];} -(void) functionPointers{ setSkinColor_Func=(void (*)(id, SEL, NSString*)) [cattle[1] methodForSelector:skin]; //IMP setSkinColor_Func = [cattle[1] methodForSelector:skin]; say_Func = [cattle[1] methodForSelector:say]; setSkinColor_Func(cattle[1],skin,@"verbose"); NSLog(@"Running as a function pointer will be more efficiency!"); say_Func(cattle[1],say); } -(void) doWithCattleId:(id) aCattle colorParam:(NSString*) color{ if(notFirstRun == NO){ NSString *myName = NSStringFromSelector(_cmd); NSLog(@"Running in the method of %@", myName); notFirstRun = YES; } NSString *cattleParamClassName = [aCattle className]; if([cattleParamClassName isEqualToString:BULL_CLASS] || [cattleParamClassName isEqualToString:CATTLE_CLASS]) { [aCattle setLegsCount:4]; if([aCattle respondsToSelector:skin]) { [aCattle performSelector:skin withObject:color]; } else { NSLog(@"Hi, I am a %@, have not setSkinColor!", cattleParamClassName); } [aCattle performSelector:say]; } else { NSString *yourClassName = [aCattle className]; NSLog(@"Hi, you are a %@, but I like cattle or bull!", yourClassName); } } @end 第六步, 打开 “05-Hello Selector.m” 作出如下 修改并且保存 #import #import "DoProxy.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; DoProxy *doProxy = [DoProxy new]; [doProxy setAllIVars]; [doProxy SELFuncs]; [doProxy functionPointers]; [pool drain]; return 0; } 第七步 ,选择屏幕 上方菜单里面 的 “Run”,然后选择 “Console”,打开了 Console 对话框之 后 , 选择对话 框上部中央的 “Build and Go”,如果不 出什么意外的 话,那么应该 出现入图 5-1 所示的 结果。 如果出现了 什么意外导 致错误的话 ,那么请仔 细检查一下 你的代码。 如果经过仔 细检查 发现 还是不 能执行的话 ,可以下载 笔者为同学 们准备的代 码。 如果笔 者的代码还 是不能执行 的话,请 告知笔者。 5.3,BOOL 类型 我们现在 打开 “DoProxy.h”文件 。“DoProxy.h”文件的第 3行到第 5行是三个 预定义的三个 字 符串的宏 。我们将在 程序当中使用 这 3个宏 ,为了实现 代码的独立性 ,在实际的 程序开发当中 , 我们 也许 考虑 使用一 个配 置的 文本文 件或 者一 个 XML来替 代这 些宏 。但是 现在 由于 笔者的 主 要目的是 讲解 Objective-C 的概念 ,为了避免 较多的代码给 大家带来理解 主题的困难 ,所以笔者 没有使用 配置文件或者 XML来表述这 些可以设定的 常量。 “DoProxy.h”的第7行对同学们来说也是老朋友了,是通知编译器,我们需要声明一个 DoProxy 类,从 NSObject 继承。 我们在第 8行遇到了 我们的一个新 的朋友, BOOL: BOOL notFirstRun; 我们定义了一个notFirstRun 的实例变量,这个变量是布尔类型的。我们的实例方法 doWithCattleId 需要被执 行多次,我们 在第一次执行 doWithCattleId 的时候需 要向控制输出 包 含 doWithCattleId 的方法名 字的字符串, 关于这个字符 串的内容,请 参考图 5-1。 好的 ,我们现在 需要看看 在Objective-C 里面BOOL 是怎样定 义的 ,我们把鼠 标移动 到BOOL 上面,然 后单击鼠标右 键选择弹出菜 单的 “Jump to Definition”,然后 Xcode 会打开 objc.h 文件 , 我们看到 下面的代码: typedef signed char BOOL;//BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"// even if -funsigned-char is used.#define OBJC_BOOL_DEFINED #define YES(BOOL)1#define NO (BOOL)0 我们看到 这段代码,我 们可以这样理 解,在 Objective-C 里面, BOOL 其实是 signed char, YES是1,NO是0。我们可 以这样给 BOOL 赋值: BOOL x = YES;BOOL y = NO; 关于 BOOL,实际上 就是一个开关 的变量,但是 我们需要注意 下面 2点: 第 一点 ,从 本质 上来 说 BOOL 是 一个 8bit 的 一个 char, 所以 我们 在把 其他 比如 说 short 或 者int 转换成为 BOOL 的时候一 定要注意。如 果 short 或者 int 的最低的 8位bit 都是 0的话 ,尽 管除了最 低的 8位以外都 不是 0,那么经过 转换之后 ,就变成了 0也就是 NO。比如说我 们有一 个int 的值是 0X1000,经过 BOOL 转换之后 就变成了 NO。 第二点 ,Objective-C 里面的所 有的逻辑判断 例如 if 语句等等 和 C语言保持 兼容 ,如果数值 不 是0判断为真 ,如果数值是 0那么就判 断为假,并不 是说定义了 BOOL 值之后就 变成了只有 1 或者 YES为真。所 以下面的代码 的判断都为真 : if(0X1000)if(2)if(-1) 5.4,SEL 类型 让我们接 着看 “DoProxy.h”文件的下 列代码: 1 id cattle[3];2 SEL say;3 SEL skin; 其中id cattle[3]定 义了 一个 数组 用于 存储 Cattle 或者Bull 对 象。 这一 行代 码估 计大 家都 很 熟悉, 笔者就不赘 述了。像这 样的传统的 数组并不能 完全满足我 们的需求, 当我们需要 做诸如 追加, 删除等操作 的时候,会 很不方便。 在随后的章 节里面笔者 将要向大家 介绍传统数 组的替 代解决方 案 NSArray。 上一段代 码的第二行和 第三行是本节 所关注的 ,就是 SEL 类型 。Objective-C 在编译的 时候 , 会 根据 方法 的名 字( 包括 参数 序列 ), 生成 一个 用 来 区分 这个 方法 的唯 一的 一个 ID, 这个 ID 就是 SEL 类型的 。我们需要 注意的是 ,只要方法 的名字 (包括参数 序列 )相同 ,那么它们 的 ID 都是相同 的 。就是 说,不管是超 类还是子类 ,不管是有 没有超类和子 类的关系 ,只要名字 相同 那么 ID就是一样 的 。除了函数 名字和 ID,编译器当 然还要把方法 编译成为机器 可以执 行的代 码,这 样,在一个 编译好的类 里面,就产 生了如下图 所示方法的 表格示意图 (本构造属 于笔者 推测,没 有得到官方证 实,所以图 5-2 为示意图 仅供参考,我 们可以暂时认 为是这样的 )。 图5-2,方法的 表格示意图 请 注意 setSkinColor 后 面有 一个 冒号 ,因 为它 是带 参数 的。 由于 存在 这样 的一 个表 格, 所 以 在程 序执 行的 时 候, 我们 可以 方 便的 通过 方法 的 名字 ,获 取到 方 法的 ID也 就是 我们 所说 的 SEL,反之亦 然。具体的使 用方法如下: 1 SEL 变量名= @selector( 方法名字);2 SEL 变量名= NSSelectorFromString( 方法名字的字符串);3 NSString *变量名= NSStringFromSelector(SEL 参数 ); 其中第 1行是直 接在程序里 面写上方法 的名字,第 2行是写 上方法名字 的字符串, 第 3行 是通过 SEL 变量获得 方法的名字 。我们得到 了 SEL 变量之后 ,可以通过 下面的调用来 给一个对 象发送消 息: [对象 performSelector:SEL 变量 withObject:参数 1 withObject:参数 2]; 这样的机 制大大的增加 了我们的程序 的灵活性,我 们可以通过给 一个方法传递 SEL 参数 , 让这个 方法动态的 执行某一个 方法;我们 也可以通过 配置文件指 定需要执行 的方法,程 序读取 配置文件 之后把方法的 字符串翻译成 为 SEL 变量然后 给相应的对象 发送这个消息 。 从效率的 角度上来说 ,执行的时 候不是通过方 法名字而是方 法 ID也就是一 个整数来查找 方 法,由于 整数的查找和 匹配比字符串 要快得多,所 以这样可以在 某种程度上提 高执行的效率 。 5.5,函数指 针 在讲解函 数指针之前 ,我们先参 看一下图 5-2,函数指针 的数值实际上 就是图 5-2 里面的地 址,有人把这 个地址成为函 数的入口地址 。在图 5-2 里面我们 可以通过方法 名字取得方法 的 ID, 同样 我们也 可以通 过方法 ID也就 是 SEL 取得 函数指 针,从 而在程 序里面 直接获 得方法 的执行 地址 。或者 函数指 针的方 法有 2种, 第一种 是传统 的 C语言 方式, 请参看 “DoProxy.h” 的下 列 代码片断 : 1 void(*setSkinColor_Func) (id, SEL, NSString*);2 IMP say_Func; 其中第 1行我们定 义了一个 C语言里面 的函数指针 ,关于 C语言里面 的函数指针的 定义以 及使用方 法 ,请参考 C语言的书 籍和参考资料 。在第一行 当中 ,值得我们 注意的是这个 函数指 针的参数 序列: 第一个参 数是 id 类型的, 就是消息的接 受对象,在执 行的时候这个 id 实际上就 是 self,因 为我们将 要向某个对象 发送消息。 第 二个 参数 是 SEL, 也是 方法 的 ID。 有的 时候 在 消息 发送 的时 候 ,我 们需 要使 用 用 _cmd 来获取方 法自己的 SEL,也就是说 ,方法的定 义体里面 ,我们可以 通过访问 _cmd 得到这个 方法 自己的 SEL。 第三个 参数是 NSString*类型的 ,我们用它 来传递 skin color。在 Objective-C 的函数 指针里 面,只有第一 个 id 和第二个 SEL 是必需的 ,后面的参 数有还是没有 ,如果有那 么有多少个要 取 决于方法 的声明。 现在我们 来介绍一下 Objective-C 里面取得 函数指针的新 的定义方法, IMP。 上面的代 码的第一行比 较复杂,令人 难以理解, Objective-C 为我们定 义了一个新的 数据类 型就是在 上面第二行代 码里面出现的 IMP。我们把鼠 标移动到 IMP上,单击右键 之后就可以看 到IMP的定义, IMP的定义如 下: typedef id (*IMP)(id, SEL,); 这个格式 正好和我们在 第一行代码里 面的函数指针 的定义是一样 的。 我们取 得了函数指 针之后,也 就意味着我 们取得了执 行的时候的 这段方法的 代码的入口 , 这样我们 就可以像普通 的 C语言函数 调用一样使用 这个函数指针 。当然我们 可以把函数指 针作 为参数 传递到其他 的方法,或 者实例变量 里面,从而 获得极大的 动态性。我 们获得了动 态性, 但是付 出的代价就 是编译器不 知道我们要 执行哪一个 方法所以在 编译的时候 不会替我们 找出错 误,我 们只有执行 的时候才知 道,我们写 的函数指针 是否是正确 的。所以, 在使用函数 指针的 时候要 非常准确地 把握能够出 现的所有可 能,并且做 出预防。尤 其是当你在 写一个供他 人调用 的接口 API 的时候, 这一点非常重 要。 5.6,Class 类型 到目 前为止 ,我们 已经知 道了对 应于方 法的 SEL 数据 类型, 和 SEL 同样 在 Objective-C 里 面我们不 仅仅可以使用 对应于方法的 SEL,对于类 在 Objective-C 也为我们 准备了类似的 机制 , Class 类型 。当一个类 被正确的编译 过后 ,在这个编 译成功的类里 面 ,存在一个 变量用于保存 这 个类 的信息 。我们 可以通 过一个 普通的 字符串 取得 这个 Class,也 可以通 过我们 生成的 对象取 得这个 Class。Class 被成功取 得之后 ,我们可以 把这个 Class 当作一个 已经定义好的 类来使用它 。 这 样的 机制 允许 我们 在程 序执 行的 过程 当中 ,可 以 Class 来 得到 对象 的类 ,也 可以 在程 序执 行 的阶段动 态的生成一个 在编译阶段无 法确定的一个 对象。 因为 Class 里面保存 了一个类的所 有信息 ,当然 ,我们也可 以取得一个类 的超类 。关于 Class 类型,具 体的使用格式 如下: 1 Class 变 量名 = [类 或者 对象 class];2 Class 变 量名 = [类 或者 对象superclass];3 Class 变量名= NSClassFromString(方法名字的字符串);4 NSString *变量名 = NSStringFromClass(Class 参数 ); 第一行代 码 ,是通过向 一个类或者对 象发送 class 消息来获 得这个类或者 对象的 Class 变量 。 第二行代 码 ,是通过向 一个类或者对 象发送 superclass 消息来获 得这个类或者 对象的超类 的 Class 变量。 第三行 代码 ,是通过 调用 NSClassFromString 函数 ,并且把 一个字符串 作为参数来 取得 Class 变量 。这个在我 们使用配置文 件决定执行的 时候的类的时 候 ,NSClassFromString 给我们带 来了 极大的方 便。 第四行 代码 ,是NSClassFromString 的反向 函数 NSStringFromClass,通过一 个 Class 类型作 为变量取 得一个类的名 字。 当 我们 在程 序里 面通 过使 用上 面的 第一 ,二 或者 第三 行代 码成 功的 取得 一个 Class 类 型的 变 量,比如说我 们把这个变量 名字命名为 myClass,那么我们 在以后的代码 种可以把 myClass 当作 一个我 们已经定义 好的类来使 用,当然我 们可以把这 个变量作为 参数传递到 其他的方法 当中让 其他的方 法动态的生成 我们需要的对 象。 5.7,DoProxy.h 里面的方 法定义 DoProxy.h 里面还有 一些实例方法 ,关于方法 的定义的格式 ,同学们可 以参照第三章 。我们 现在要对 DoProxy.h 里面定义 的方法的做一 下简要的说明 。 1 -(void) doWithCattleId:(id) aCattle colorParam:(NSString*) color;2 -(void) setAllIVars;3 -(void) SELFuncs;4 -(void) functionPointers; 第一行的 方法 ,是设定 aCattle,也就是 Cattle 或者 Bull 对象的属 性 ,然后调用 saySomething 方法,实 现控制台的打 印输出。 第二行的 方法,是把我 们定义的 DoProxy 类里面的 一些变量进行 赋值。 第三行的 方法,是调用 doWithCattleId 方法。 第四行的 方法,是调用 了函数指针的 方法。 好的,我 们把 DoProxy.h 的内容介 绍完了,让我 们打开 DoProxy.m。 5.8,DoProxy.m 的代码说 明 有了 DoProxy.h 的说 明, 同学 们理解 DoProxy.m 将是 一件 非常 轻松的 事情 ,让 我们坚 持一 下把这个 轻松的事情搞 定。由于篇幅 所限,笔者在 这里的讲解将 会省略掉非本 章的内容。 DoProxy.m 代码如下 : 1 #import "DoProxy.h" 2 #import "Cattle.h" 3 #import "Bull.h" 4 5 @implementation DoProxy 6 -(void) setAllIVars 7 { 8 cattle[0] = [Cattle new]; 9 10 bullClass = NSClassFromString(BULL_CLASS); 11 cattle[1] = [bullClass new];12 cattle[2] = [bullClass new];13 14 say = @selector(saySomething); 15 skin = NSSelectorFromString(SET_SKIN_COLOR); 16 } 17 -(void) SELFuncs 18 { 19 [self doWithCattleId:cattle[0] colorParam:@"brown"]; 20 [self doWithCattleId:cattle[1] colorParam:@"red"]; 21 [self doWithCattleId:cattle[2] colorParam:@"black"]; 22 [self doWithCattleId:self colorParam:@"haha"]; 23 } 24 -(void) functionPointers 25 { 26 setSkinColor_Func=(void (*)(id, SEL, NSString*)) [cattle[1] methodForSelector:skin]; 27 //IMP setSkinColor_Func = [cattle[1] methodForSelector:skin]; 28 say_Func = [cattle[1] methodForSelector:say]; 29 setSkinColor_Func(cattle[1],skin,@"verbose"); 30 NSLog(@"Running as a function pointer will be more efficiency!"); 31 say_Func(cattle[1],say); 32 } 33 -(void) doWithCattleId:(id) aCattle colorParam:(NSString*) color 34 { 35 if(notFirstRun == NO) 36 { 37 NSString *myName = NSStringFromSelector(_cmd); 38 NSLog(@"Running in the method of %@", myName); 39 notFirstRun = YES; 40 } 41 42 NSString *cattleParamClassName = [aCattle className]; 43 if([cattleParamClassName isEqualToString:BULL_CLASS] || 44 [cattleParamClassName isEqualToString:CATTLE_CLASS]) 45 { 46 [aCattle setLegsCount:4]; 47 if([aCattle respondsToSelector:skin]) 48 { 49 [aCattle performSelector:skin withObject:color]; 50 } 51 else 52 { 53 NSLog(@"Hi, I am a %@, have not setSkinColor!", cattleParamClassName); 54 } 55 [aCattle performSelector:say]; 56 } 57 else 58 { 59 NSString *yourClassName = [aCattle className]; 60 NSLog(@"Hi, you are a %@, but I like cattle or bull!", yourClassName); 61 } 62 } 63 @end 第10 行代码是 通过一个预定 义的宏 BULL_CLASS 取得 Bull 的Class 变量。 第11 和12 行代 码是 使用 bullClass 来初 始化 我们 的 cattle 实例 变量 数组 的第 2和第 3个元 素。 第14 行是通过 @selector 函数来取 得 saySomething 的SEL 变量。 第15 行是通过向NSSelectorFromString 传递预定义的宏SET_SKIN_COLOR 来取得 setSkinColor 的SEL 变量。 第22 行,笔者 打算 “戏弄 ”一下 doWithCattleId,向传递 了不合适的参 数。 第26 行,笔者取得 了传统的 C语言的函 数指针 ,也是使用 了第 5.5 节所述的 第一种取得的 方法。 第28 行,笔者 通过 5.5 节所述的 第二种取得的 方法得到了函 数指针 say_Func。 第29 行和 31 行分别执 行了分别在第 26 行和 28 行取得的 函数指针。 第35 行是一个 BOOL 型的实例 变量 notFirstRun 。当对象被 初始化之后 ,确省的值 是 NO。 第一次执 行完毕之后, 我们把这个变 量设定成为 YES,这样就 保证了花括号 里面的代码只 被执 行一次。 第37 行 我们 通过 _cmd 取 得了 doWithCattleId 这 个方 法名 字 用于 输出 。当 然 同学 们在 设计 方法的 提供给别人 使用的时候 ,为了防止 使用方法的 人把这个方 法本身传递 进来造成死 循环, 需要使用 _cmd 这个系统 隐藏的变量判 断一下 。笔者在这 里没有做出判 断 ,这样写从 理论上来说 存在一定 的风险。 第42 行,我们 通过向对象发 送 className 消息来取 得这个对象的 类的名字。 第43 行和第 44 行,我们通 过 NSString 的方法 isEqualToString 来判断 取得的类的 名字是否 在我们事 先想象的范围 之内,我们只 希望接受 Bull 或者 Cattle 类的对象 。 第46 行, 本来 我们 想通过 SEL 的方 式来 进行 这个牛 股的 设定 ,但是 由于 它的 参数不 是 从 NSObject 继承下来 的 ,所以我们 无法使用 。我们会有 办法解决这个 问题的 ,我们将在 后面的章 节里面介 绍解决这个问 题的方法。 第47 行的代 码,有一个 非常重要 NSObject 的方法 respondsToSelector,通过 向对象发送 这 个消息 ,加上一个 SEL,我们可以 知道这个对象 是否可以相应 这个 SEL 消息 。由于我们 的 Cattle 无法相应 setSkinColor 消息 ,所以如果 对象是 Cattle 类生成的 话 ,if 语句就是 NO所以花括 号里 面的内容 不会得到执行 。 第59 行,我们 通过类的名字 发现了一个假 冒的 Cattle,我们把 这个假冒的家 伙给揪出来, 然后实现 了屏幕打印。 5.9,本章总 结 本章给同 学们介绍了几 个新的数据类 型 ,以及使用 方法 ,这些数据 类型分别是 BOOL,SEL, Class,IMP。 本章的 内容很重要 ,希望同学 们花一点时 间仔细的理 解一下。本 章内容也是 理解下一章 内 容的基础 ,下一章我们 将要讲述 NSObject 的奥秘。 系列文章 : Objective-C 2.0 with Cocoa Foundation--- 1,前言 Objective-C 2.0 with Cocoa Foundation --- 2,从Hello,World!开始 Objective-C 2.0 with Cocoa Foundation --- 3,类的声明 和定义 Objective-C 2.0 with Cocoa Foundation--- 4,继承 Objective-C 2.0 with Cocoa Foundation--- 6,NSObject 的奥秘 Objective-C 2.0 with Cocoa Foundation--- 7,对象的 初始化以及实 例变量的作用 域 Objective-C 2.0 with Cocoa Foundation--- 6,NSObject 的奥秘 在上 一章里 面,笔 者向大 家介绍 了在 Objective-C 里面 的几个 非常重 要的概 念, 简单 的说就 是 SEL,Class 和IMP。 我们 知道 Objective-C 是C语 言的 扩展 ,有 了这 3个 概念 还有 我们 以前 讲 过的继承 和封装的概念 ,Objective-C 发生了翻 天覆地的变化 ,既兼容 C语言的高 效特性又实现 了面向对 象的功能。 Objective-C 从本质上 来说 ,还是 C语言的 。那么内部 究竟是怎样实 现 SEL,Class 和IMP, 还有封装 和继承的?为 了解答这个问 题 ,笔者决定 在本章向大家 概要的介绍一 下 Objective-C 的 最主要的 一个类, NSObject。 不过说 实在话,如 果同学们觉 得本章的内 容比较晦涩 难懂的话, 不阅读本章 的内容丝毫 不 会对写程 序产生任何不 良的影响 ,但是如果 掌握了本章的 内容的话 ,对加深对 Objective-C 的理 解,对于 今后笔者将要 讲述的内容而 言,将会是一 个极大的促进 。 6.1,本章程 序的执行结果 在本 章里面 ,我们 将要继 续使用 我们在 前面几 章已经 构筑好 的类 Cattle 和Bull。由 于在现 在的 Xcode 版本 里面, 把一些 重要的 东西比 如说 Class 的原 型定义 都放到 了 LIB 文件 里面, 所 以这些东 西的具体的定 义,对于我们 来说是不可见 的。 我们首先 把第 4章的代码 打开,然后打 开 “Cattle.h” 文件,把 鼠标移动到 “NSObject”上面 , 单 击 鼠标 右 键, 在 弹 出菜 单 里面 选 择 “Jump to Definition”。 然 后会 弹 出一 个 小 菜单 , 我们 选 择 “interface NSObject”。我们可 以看到如下代 码 @interface NSObject { Class isa; 我们 知道 了, 所谓的 NSObject 里面 只有 一个 变量, 就是 Class 类型 的 isa。isa 的英 文的 意思 就是is a pointer 的 意思 。也 就是 说 NSObject 里 面只 有一 个实 例变 量 isa。 好的 ,我 们需 要知 道 Class 是 一个 什 么 东西 , 我们 把 鼠 标移 动 到 “Class”上 面, 单 击 鼠标 右 键, 在 弹 出菜 单 里面 选 择 “Jump to Definition”,我们看 到了如下的代 码: typedef struct objc_class *Class;typedef struct objc_object { Class isa;} *id;... 我们在这里知道了,Class 实际上是一个objc_class 的指针类型,我们把鼠标移动到 “objc_class”上面 ,单击鼠标 右键 ,在弹出菜 单里面选择 “Jump to Definition”,发现我们 还是在这 个窗口里 面 ,Xcode 并没有把 我们带到 objc_class 的定义去 ,所以我们 无从知道 objc_class 内部 究竟是一 个什么样的东 西。 笔者顺便 提一下,大家 也许注意到了 id 的定义, id 实际上是 objc_object 结构的一 个指针 , 里面只有 一个元素那就 是 Class。那么根据 上面我们看到 的 ,所谓的 id 就是 objc_class 的指针的 指针。让 我们回忆一下 下面的代码: id cattle = [Cattle new]; 这句话 是在初始化 和实例话 cattle 对象, 这个过程, 实际上可以 理解为, runtime 为我们 初 始 化好 了 Class 的 指针 ,并 且把 这个 指针 返回 给我 们。 我们 初始 化对 象完 成了 之后 ,实 际上 我 们得到的 对象就是一个 指向这个对象 的 Class 指针。 让我们在回过头来说说这个神秘的Class,我们无法在Xcode 里面看到Class 也就是 objc_class 的定 义。庆 幸的是 这部分 的定义 是 GCC 代码 ,是开 源的。 笔者下 载了开 源的代 码之 后 ,把 开源 的代 码作 了一 些小 小的 调整 ,然 后把 Class 的 定义 等等 放到 了我 们的 工程 文件 里面 去,通过类型 转化之后 ,我们终于 可以看到 Class,SEL,还有 isa 等等过去 对我们来说比 较 “神 秘”的东西的 真正面目。 我们在 前面几章里 面在每一个 章的第一节 里面都要介 绍一下本章 程序执行结 果的屏幕拷 贝, 本章也是 一样 ,但是本章 的执行结果非 常简单 。因为对于 本章而言重点 应该是放在对 NSObject 机制的理 解上。 图片看不 清楚?请点击 这里查看原图 (大图 )。 图6-1,本章程 序运行结果 大家看 到本章程序 的运行结果 的屏幕拷贝 的时候,也 许会觉得很 无趣,因为 单单从结果 画 面,我们没有 发现任何令人 感到很有兴趣 的东西 ,相反 ,都是同学 们已经很熟悉 的一些老面孔 。 但是本 章所要讲述 的东西也许 是同学们在 其他语言里 面从来没有 遇到过的东 西,这些东 西将会 令人感到 新鲜和激动。 6.2,实现步 骤 第一步, 按照我们在第 2章所述的 方法,新建一 个项目,项目 的名字叫做 06-NSObject。 第二 步, 按照 我们在 第 4章的 4.2 节的 第二 ,三 ,四步 所述 的方 法,把 在第 4章已 经使 用 过的 “Cattle.h”,“Cattle.m”,“Bull.h”还有 “Bull.m” 导入本章 的项目里面。 第三步 ,把鼠标移 动到项目浏览 器上面的 “Source”上面 ,然后在弹 出的菜单上面 选择 “Add”, 然 后在 子菜 单里 面选 择 “New File”, 然后 在新 建文 件对 话框 的左 侧最 下面 选择 “Other”, 然后 在 右侧窗口选择“Empty File”,选择“Next”,在“New File”对话框里面的“File Name”栏内输入 “MyNSObject.h”。然后输入 (或者是拷 贝也可以 ,因为这是 C的代码 ,如果你很 熟悉 C语言的 话,可以 拷贝一下节省 时间)如下代 码: #include typedef const struct objc_selector { void *sel_id; const char *sel_types;} *MySEL;typedef struct my_objc_object { struct my_objc_class* class_pointer;} *myId; typedef myId (*MyIMP)(myId, MySEL, ); typedef char *STR;/* String alias */ typedef struct my_objc_class *MetaClass; typedef struct my_objc_class *MyClass;struct my_objc_class { MetaClass class_pointer; struct my_objc_class* super_class; const char* name; long version; unsigned long info; long instance_size; struct objc_ivar_list* ivars; struct objc_method_list* methods; struct sarray * dtable; struct my_objc_class* subclass_list; struct my_objc_class* sibling_class; struct objc_protocol_list *protocols; void* gc_object_type;}; typedef struct objc_protocol { struct my_objc_class* class_pointer; char *protocol_name; struct objc_protocol_list *protocol_list; struct objc_method_description_list *instance_methods, *class_methods; } Protocol; typedef void* retval_t; typedef void(*apply_t)(void); typedef union arglist { char *arg_ptr; char arg_regs[sizeof (char*)];} *arglist_t; typedef struct objc_ivar* Ivar_t; typedef struct objc_ivar_list { int ivar_count; struct objc_ivar { const char* ivar_name; const char* ivar_type; int ivar_offset; } ivar_list[1]; } IvarList, *IvarList_t;typedef struct objc_method { MySEL method_name; const char* method_types; MyIMP method_imp; } Method, *Method_t; typedef struct objc_method_list { struct objc_method_list* method_next; int method_count; Method method_list[1]; } MethodList, *MethodList_t; struct objc_protocol_list { struct objc_protocol_list *next; size_t count; Protocol *list[1]; }; 第四步, 打开 06-NSObject.m 文件,输 入如下代码并 且保存 #import #import "Cattle.h"#import "Bull.h" #import "MyNSObject.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle = [Cattle new]; id redBull = [Bull new]; SEL setLegsCount_SEL = @selector(setLegsCount:); IMP cattle_setLegsCount_IMP = [cattle methodForSelector:setLegsCount_SEL]; IMP redBull_setLegsCount_IMP = [redBull methodForSelector:setLegsCount_SEL]; [cattle setLegsCount:4]; [redBull setLegsCount:4]; [redBull setSkinColor:@"red"]; Class cattle_class = cattle->isa; MyClass my_cattle_class = cattle->isa; SEL say = @selector(saySomething); IMP cattle_sayFunc = [cattle methodForSelector:say]; cattle_sayFunc(cattle, say); Class redBull_class = redBull->isa; MyClass my_redBull_class = redBull->isa; IMP redBull_sayFunc = [redBull methodForSelector:say]; redBull_sayFunc(redBull, say); [pool drain]; return 0;} 第五步, 在 06-NSObject.m 文件的窗 口的 “[pool drain];”代码的左 侧单击一下窗 口的边框 , 确认一下 是否出现一个 蓝色的小棒棒 ,如果有的话 那么断点被选 择好了。如图 6-2 所示 图片看不 清楚?请点击 这里查看原图 (大图 )。 图6-2,选择执 行断点 第六步 ,选择 Xcode 上面的菜 单的 “Run”,然后选择 “Debuger”,在Debuger 窗口里面 选择 “Build and Go”。 好的, 大家就停在 这里,不要 做其他的操 作,我们把 程序中断在 程序几乎执 行到最后的 断 点上,我 们将要通过 Debuger 来看看 Objective-C 内部究竟 发生了什么样 的奇妙的魔法 。 注意 在从编译 到执行的过程 当中 ,会出现一 些警告 。由于本章 程序指示用来 阐述一些 NSObject 内部的 东西,所以 请忽略掉这 些警告。当 然,我们在 写自己的程 序的时候, 编译产生的 警告一 般是不能 被忽略的。 6.3,超类方 法的调用 我们现在 打开 “06-NSObject.m”文件,发 现下面的代码 : SEL setLegsCount_SEL = @selector(setLegsCount:); IMP cattle_setLegsCount_IMP = [cattle methodForSelector: setLegsCount_SEL]; IMP redBull_setLegsCount_IMP = [redBull methodForSelector:setLegsCount_SEL]; 这一段代 码,对同学们 来说不是什么 新鲜的内容了 ,我们在第 5章里面已 经讲过,这个 是 SEL 和IMP的概念。 我们在这里取 得了 cattle 对象和 redBull 对象的 setLegsCount:的函数指 针 。 如 果大 家现 在 已经 不在 Debuger 里 面的 话, 那 么请 选择 Xcode 菜 单里 面的 , “Run”然 后选 择 “Debuger”。 我们 注意到 在 Debuger 里面 ,cattle_setLegsCount_IMP 的地 址和 redBull_setLegsCount_IMP 是完全一 样的,如图 6-3 所示: 图片看不 清楚?请点击 这里查看原图 (大图 )。 图6-3,cattle_setLegsCount_IMP 和redBull_setLegsCount_IMP 的地址。 注意 由于环境 和执行的时候 的内存情况不 同,所以同学 们的电脑上显 示的地址的数 值可能和 图 6-3 的数值不 一样。 他们的 地址完全一 样,说明他 们使用的是 相同的代码 段。这种结 果是怎样产 生的呢?大 家 请打开 “MyNSObject.h”,参照下 列代码: struct my_objc_class { MetaClass class_pointer; struct my_objc_class* super_class; const char* name; long version; unsigned long info; long instance_size; struct objc_ivar_list* ivars; struct objc_method_list* methods; struct sarray * dtable; struct my_objc_class* subclass_list; struct my_objc_class* sibling_class; struct objc_protocol_list *protocols; void* gc_object_type;}; 笔 者在 这里 把开 源代 码的 名字 的定 义加 上了 “my_”前 缀, 仅仅 是为 了使 编译 不出 现问 题。 这 段代码实 际上就是 Class 的实际上 定义的部分。 我 们注 意到 这里 的 methods 变 量, 里面 包存 的就 是类 的方 法名 字( SEL) 定义 ,方 法的 指 针地址 (IMP)。当我们 有执行 IMP cattle_setLegsCount_IMP = [cattle methodForSelector:setLegsCount_SEL]; 的时候 ,runtime 会通过 dtable 这个数组 ,快速的查 找到我们需要 的函数指针 ,查找函数 的 定义如下 : __inline__ IMPobjc_msg_lookup(id receiver, SEL op){ if(receiver) return sarray_get(receiver->class_pointer->dtable, (sidx)op); else return nil_method; 好的,现在我们的cattle_setLegsCount_IMP 没有问题了,那么redBull_setLegsCount_IMP 怎么办?在Bull 类里面我们并没有定义实例方法setLegsCount:,所以在Bull 的Class 里面, runtime 难道找 不到 setLegsCount:么?答 案是 ,是的 runtime 直接找 不到 ,因为我 们在 Bull 类里 面根本就 没有定义 setLegsCount:。 但是 ,从结 果上来 看很明 显 runtime 聪明 的找到 了 setLegsCount:的地 址, runtime 是怎 样找 到的?答 案就在: struct my_objc_class* super_class; 当寻找 失败了之后 ,runtime 会去 Bull 类的超 类 cattle 里面去 寻找 ,在cattle 类里面 runtime 找到了setLegsCount: 的执行地址入口,所以我们得到了redBull_setLegsCount_IMP 。 redBull_setLegsCount_IMP 和cattle_setLegsCount_IMP 都是在 Cattle 类里面 定义的 ,所以他 们的 代码的地 址也是完全一 样的。 我 们现 在假 设 ,如 果 runtime 在cattle 里 面也 找不 到 setLegsCount:呢 ?没 有关 系 , cattle 里面 也有超 类的 ,那就是 NSObject。所以 runtime 会去 NSObject 里面寻 找 。当然 ,NSObject 不会神 奇到可以 预测我们要定 义 setLegsCount:所以 runtime 是找不到 的。 在这 个时候 , runtime 并没 有放弃 最后的 努力, 再没有 找到对 应的方 法的时 候, runtime 会 向对象发送一个forwardInvocation:的消息,并且把原始的消息以及消息的参数打成一个 NSInvocation 的一 个对 象里 面,作 为 forwardInvocation:的唯 一的 参数 。 forwardInvocation:本身 是在 NSObject 里面定义 的 ,如果你需 要重载这个函 数的话 ,那么任何 试图向你的类 发送一个没 有定 义的消 息的话 ,你都 可以在 forwardInvocation:里面 捕捉到 ,并且 把消息 送到某 一个安 全的 地方,从 而避免了系统 报错。 笔者 没有 在本 章代码 中重 写 forwardInvocation:,但 是在 重写 forwardInvocation:的时 候一 定 要注意避 免消息的循环 发送 。比如说 ,同学们在 A类对象的 forwardInvocation 里面 ,把A类不 能响应的 消息以及消息 的参数发给 B类的对象 ;同时在 B类的 forwardInvocation 里面把 B类不 能响应的 消息发给 A类的时候 ,容易形成 死循环 。当然一个 人写代码的时 候不容易出现 这个问 题, 当你在 一个工 作小组 里面做 的时候 ,如果 你重写 forwardInvocation:的时 候,需 要和小 组的 其他人达 成共识,从而 避免循环调用 。 6.4,重载方 法的调用 让我们继 续关注 “06-NSObject.m”文件,请 大家参考一下 下面的代码: 1 Class cattle_class = cattle->isa; 2 MyClass my_cattle_class = cattle->isa; 3 SEL say = @selector(saySomething); 4 IMP cattle_sayFunc = [cattle methodForSelector:say]; 5 cattle_sayFunc(cattle, say); 6 7 Class redBull_class = redBull->isa; 8 MyClass my_redBull_class = redBull->isa; 9 10 IMP redBull_sayFunc = [redBull methodForSelector:say]; 11 redBull_sayFunc(redBull, say); 本节 的内 容和 6.3 节的 内容 比较 类似, 关于 代码 部分笔 者认 为就 不需要 解释 了, 如果同 学们 有所不熟 悉的话,可以 参考一下第 5章的内容 。 在 我们 的 Cattle 类和Bull 类 里面 ,都 有 saySometing 这 个实 例方 法。 我们 知道 只要 方法 的 定义 相同, 那么它 们的 SEL 是完 全一样 的。我 们根据 一个 SEL say,在 cattle 和redBull 对象 里 面找到了 他们的函数指 针。根据 6.3 节的讲述 ,我们知道当 runtime 接收到寻 找方法的时候 ,会 首先在这 个类里面寻找 ,寻找到了 之后寻找的过 程也就结束了 ,同时把这 个方法的 IMP返回给 我们 。所以 ,在上面的 代码里面的 cattle_sayFunc 和redBull_sayFunc 应该是不 一样的 ,如图 6-4 所示: 图片看不 清楚?请点击 这里查看原图 (大图 )。 图6-4,cattle_sayFunc 和redBull_sayFunc 的地址 6.5,超类和 子类中的 Class 在类进行 内存分配的时 候 ,对于一个 类而言 ,runtime 需要找到 这个类的超类 ,然后把超 类 的Class 的 指 针 的地 址 赋 值 给 isa 里面的super_class。 所 以 ,我 们 的 cattle 里面的Class 应该和 redBull 里面的 Class 里面的 super_class 应该是完 全相同的,请 参照图 6-5: 图6-5,cattle 里面的 Class 和redBull 里面的 Class 里面的 super_class 6.6,实例变 量的内存分配 的位置 我们先 来回忆一下 对象是怎样 被创建的。 创建对象的 时候,类的 内容需要被 调入到内存 当 中我们称 之为内存分配 (Allocation),然后需要 把实体变量进 行初始化 (Initialization),当这些步 骤都结束 了之后,我们 的类就被实例 化了,我们把 实例化完成的 类叫做对象 (Object)。 对于内存 分配的过程 ,runtime 需要知道 分配多少内存 还有各个实例 变量的位置 。我们回到 “MyNSObject.h”,参照如 下代码: 1 typedef struct objc_ivar* Ivar_t; 2 typedef struct objc_ivar_list { 3 int ivar_count; 4 struct objc_ivar { 5 const char* ivar_name; 6 const char* ivar_type; 7 intivar_offset; 8 }ivar_list[1]; 9 } IvarList, *IvarList_t; 我 们仔 细 看 看第 5行的ivar_name, 顾名 思 义 这个 是 实例 变 量 的名 字 ,第 6行的ivar_type 是实例变 量的类型 ,第7行的 ivar_offset,这个就是 位置的定义 。runtime 从类的 isa 里面取得 了 这些信息 之后就知道了 如何去分配内 存。我们来看 看图 6-6: 图6-6,实例变 量在内存中的 位置 在cattle 里面 ,我们看到 了第一个实例 变量是 isa,第二个就 是我们定义的 legsCount。其中 isa 是超类的 变量 ,legsCount 是Cattle 类的变量 。我们可以 看出来 ,总是把超 类的变量放在 前头 , 然后是子 类的变量。 那么对于 redBull 而言是什 么样子呢?我 们来看看图 6-7 图片看不 清楚?请点击 这里查看原图 (大图 )。 图6-7,redBull 里面的实 例变量的位置 我们通 过 图6-7 可以发 现redBull 的Class 里面 的skinColor 的位置 偏移 是8,很明显 ,runtime 为isa 和legsCount 预留了 2个位置。 6.7 本章总结 非常感谢 大家! 在本章里 面 ,笔者通过 一个小小的 “把戏 ”为同学们 揭开了 NSObject 的神秘的 面纱 。本章的 内容 ,虽然对理 解 Objective-C 不是必需 的 ,但是对以 后的章节的内 容的理解会有 一个非常好的 辅助作用 ,希望同学们 花费一点点心 思和时间阅读 一下。 另外, 笔者需要强 调一下,由 于笔者没有 得到官方的 正式的文档 说明,所以 笔者不能保 证 本章的内 容是完整而且 准确的,希望 大家谅解。 系列文章 : Objective-C 2.0 with Cocoa Foundation--- 1,前言 Objective-C 2.0 with Cocoa Foundation --- 2,从Hello,World!开始 Objective-C 2.0 with Cocoa Foundation --- 3,类的声明 和定义 Objective-C 2.0 with Cocoa Foundation--- 4,继承 Objective-C 2.0 with Cocoa Foundation--- 5,Class 类型,选 择器 Selector 以及函数 指针 Objective-C 2.0 with Cocoa Foundation--- 7,对象的 初始化以及实 例变量的作用 域 Objective-C 2.0 with Cocoa Foundation--- 7, 对象的初 始化以及实例 变量的作用域 到目前为 止,我们都使 用的是下列方 式创建对象 [类名 new]; 这种 new 的方式 ,实际上是 一种简化的方 式 。笔者在这 里总结一下前 面几章里面曾 经提到 过关于创 建对象的 2个步骤: 第一步是 为对象分配内 存也就是我们 所说的 allocation,runtime 会根据我 们创建的类的 信息 来 决定 为对 象分 配多 少内 存。 类的 信息 都保 存在 Class 里 面, runtime 读取Class 的 信息 ,知 道 了各个 实例变量的 类型,大小 ,以及他们 的在内存里 面的位置偏 移,就会很 容易的计算 出需要 的内存的 大小。分配内 存完成之后, 实际上对象里 面的 isa 也就被初 始化了, isa 指向这个 类 的 Class。类里面 的各个实例变 量,包括他们 的超类里面的 实例变量的值 都设定为零。 需要注 意的是,分 配内存的时 候,不需要 给方法分配 内存的,在 程序模块整 体执行的时 候 方法部 分就作为代 码段的内容 被放到了内 存当中。对 象的内容被 放到了数据 段当中,编 译好的 方法的汇 编代码被放到 了代码段当中 。在 Objective C里面,分 配内存使用下 列格式: id 对象名 =[类名 alloc]; NSObject 已经 为我 们提 供了诸 如计 算内 存空间 大小 以及 初始化 isa 还有 把各 个实 例变量 清 零,毫无疑问 NSObject 已经非常 出色的完成了 内存分配的工 作 ,在一般情 况下 ,我们不需 要重 写alloc 方法。 第二 步是要 对内存 进行初 始化也 就是我 们所说 的 Initialization。初始 化指的 是对实 例变量 的初始化 。虽然在 alloc 方法里面 已经把各个实 例变量给清零 了 ,但是在很 多情况下 ,我们的实 例变量 不能是零( 对于指针的 实例变量而 言,就是空 指针)的, 这样就需要 我们对实例 变量进 行有意义 的初始化。 按照 Objective-C 的约定 ,当初始化 的时候不需要 参数的话 ,就直接使 用 init 方法来初 始化 : [对象名字 init]; init 是一个定 义在 NSObject 里面的一 个方法 ,NSObject 明显无法 预测到派生类 的实例变量 是 什么 ,所 以同 学们 在自 己的 类里 面需 要重 载一 下 init 方 法, 在 init 方 法里 面把 实例 变量 进行 初始化。 但是,需要强调的是,由于某种原因我们的init 也许失败了,比如说我们需要读取 CNBLOGS.COM 的 某个 RSS, 用这 个 RSS 来 初始 化我 们的 对象 ,但 是由 于用 户的 网络 连接 失 败所 以我们 的 init 也许 会失败 ,在手 机应用 当中的 一些极 端的情 况下比 如说有 同学写 一个读 取 网页内容 的程序 ,在网页内 容非常大的时 候 ,那么 alloc 也有可能 会失败 ,为了可以 方便的捕获 这些失败 ,所以我们在 程序当中需要 把上面的过程 写在一起: id 对象名 = [[类名 alloc] init];if (对象名 )else 加上了上 面 的if 语句我们 的初始化过程 就是完美的 ,当然我们 有的时候不需 要这 个if 语句 。 当我们的 alloc 和init 永远不会 失败的时候 。关于初始 化的时候的错 误捕获 ,笔者将在 后面的章 节里面论 述。 为了我们 写程序方便和 简洁 ,在创建一 个从 NSObject 派生的类 的对象的时候 ,苹果公司 把 alloc 和init 简化 成为 new,我 们在程 序代码 当中使 用任何 一种方 式都是 可以的 ,具体 怎么写 是 同学们的 喜好和自由。 到这里 ,有同学会 问 ,如果我们 的 init 需要参数 怎么办?按照 Objective-C 的约定 ,我们需要 使用 initWith...。也就是 带参数的变量 初始化,这个 也是本章的主 要内容。 本章在讲 述 initWith 的同时, 也将会顺便的 给大家介绍一 下实例变量的 作用域。 7.1,本章程 序的执行结果 在本 章里面 ,我们 将要继 续使用 我们在 第 4章已 经构筑 好的类 Cattle 和Bull。从 一般的 面 向对象 的角度上来 说,是不鼓 励我们改写 已经生效的 代码的。但 是本章的目 的是为了使 同学们 可以 很好的 理解主 题,所 以笔者 在这里 暂时违 反一下 规则改 写了一 下 Cattle 类, 在里面 追加 了 initWith 方法,笔 者也在 Cattle 类里面追 加了一些实例 变量为了阐述 实例变量的作 用域的问题 。 由于在 Cattle 类里面笔 者追加了一些 东西,所以在 Bull 类里面改 写了 saySomething 这个函数 , 让我们的 Bull 可以说更 多的内容。我 们的 redBull 是这样说 的: 图片看不 清楚?请点击 这里查看原图 (大图 )。 图7-1,本章程 序的执行结果 再次强调 在实际的编程 过程中 ,尤其是写 大型程序多人 合作的时候 ,除非发现 BUG,否则 不要改 写已经生效 的代码。这 样会产生一 些意想不到 的结果,从 而使其他的 弟兄们或者 姐妹们 对你充满 怨言。 7.2,实现步 骤 第一步,按照我们在第2章所述的方法,新建一个项目,项目的名字叫做 07-InitWithAndIvarScope。 第二 步, 按照 我们在 第 4章的 4.2 节的 第二 ,三 ,四步 所述 的方 法,把 在第 4章已 经使 用过 的“Cattle.h”,“Cattle.m”,“Bull.h”还有“Bull.m”,导 入本 章的 项目 里面 。然 后把 第 6章 里面 的 “MyNSObject.h”也导入到 项目当中。 第三步, 打开 “Cattle.h”,修改成 为下面的代码 并且保存: #import @interface Cattle : NSObject { int legsCount; @private bool gender; //male = YES female = NO @protected int eyesCount; @public NSString *masterName;} -(void)saySomething; -(void)setLegsCount:(int) count; -(id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName;@end 第4步,打开 “Cattle.m”,修改成 下面的代码并 且保存: #import "Cattle.h" @implementation Cattle-(void) saySomething { NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount);} -(void) setLegsCount:(int) count { legsCount = count;}-(id)init{ [super init]; return [self initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"somebody"];} -(id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName{ legsCount = theLegsCount; gender = theGender; eyesCount = theEyesCount; masterName = theMasterName; return self;} @end 第五步, 打开 “Bull.m”,,修改成 下面的代码并 且保存: #import "Bull.h" @implementation Bull-(void) saySomething{ NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); //List below is illegal //NSLog(@"My gender is %@",gender ?@"male" :@"female");} -(NSString*) getSkinColor{ return skinColor;} -(void) setSkinColor:(NSString *) color{ skinColor = color;} @end 第六步, 打开 “07-InitWithAndIvarScope.m”,修改成 下面的代码并 且保存: #import #import "Bull.h"#import "Cattle.h" #import "MyNSObject.h"int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Bull *redBull = [[Bull alloc] initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"that cowboy"]; [redBull setSkinColor:@"red"]; [redBull saySomething]; //legal, but not good redBull->masterName = @"that cowgirl"; //legal, but bad //redBull->eyesCount = 3; //Trying to access a private ivar, VERY bad thing //MyClass bullClass = redBull->isa; bool *redBullGender = (bool *)(redBull) + 8; NSLog(@"My gender is %@",*redBullGender ? @"male" :@"female"); [pool drain]; return 0;} 第七步, 选择屏幕上方 菜单里面的 “Run”,然后选 择 “Console”,打开了 Console 对话框之 后 , 选择对话 框上部中央的 “Build and Go”,如果不 出什么意外的 话,那么应该 出现入图 7-1 所示的 结果。 如果出现了 什么意外导 致错误的话 ,那么请仔 细检查一下 你的代码。 如果经过仔 细检查 发现 还是不 能执行的话 ,可以下载 笔者为同学 们准备的代 码。 如果笔 者的代码还 是不能执行 的话,请 告知笔者。 7.3,实例变 量的作用域 (Scope) 对于 Objective-C 里面的类 的实例变量而 言 ,在编译器 的范围里面 ,是有作用 域的 。和其他 的语言一 样, Objective-C 也支持 public,private 还有 protected 作用域限 定。 如果一个 实例变量没有 任何的作用域 限定的话,那 么缺省就是 protected。 如果一 个实例变量 适用于 public 作用域 限定,那么 这个实例变 量对于这个 类的派生类 ,还 有类外的 访问都是允许 的。 如果一个 实例变量适用 于private 作用域限 定 ,那么仅仅 在这个类里面 才可以访问这 个变量 。 如果一个 实例变量适用 于 protected 作用域限 定 ,那么在这 个类里面和这 个类的派生类 里面 可以访问 这个变量,在 类外的访问是 不推荐的。 我们来看 看 “Cattle.h”的代码片 断: 1 int legsCount; 2 @private 3 bool gender; //male = YES female = NO 4 @protected 5 int eyesCount; 6 @public 7 NSString *masterName; 第一行的 legsCount 的前面没 有任何作用域 限定,那么它 就是 protected 的。 第二行是在说从第二行开始的实例变量的定义为private 的,和其他的关键字一样, Objective-C 使用 @来进行编 译导向。 第三行的 gender 的作用域 限定是 private 的,所以 它适用于 private 作用域限 定。 第四行是 在说从第四行 开始的实例变 量的定义为 protected 的,同时第二 行的 private 的声明 作废。 第五行的 eyesCount 的作用域 限定是 protected 的,所以 它适用于 protected 作用域限 定。 第六行是 再说从第六行 开始的实例变 量的定义为 public 的,同时第四 行的 protected 的声明 作废。 第七行的 masterName 的作用域 限定是 public 的,所以 它适用于 public 作用域限 定。 我 们 再 来 看看 在 派 生 类 当 中, private,protected 还有public 的表现。Bull 类继承了Cattle 类,笔者 改写了一下 “Bull.m”用来说明 作用域的问题 ,请参看下面 的代码: 1 -(void) saySomething 2 { 3 NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); 4 NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); 5 //List below is illegal 6 //NSLog(@"My gender is %@",gender ?@"male" :@"female"); 7 } 我们来看 看第 3还有第 4行代码, 我们可以访问 legsCount,eyesCount 还有 masterName。 在第 6行代码当 中 ,我们试图 访问 gender 这个 Cattle 的私有 (private)属性 ,这行代码 产生了 编译错误 ,所以我们不 得不注释掉第 6行代码。 好的,我们再来看看类的外部private,protected 还有public 的表现。请同学们打开 “07-InitWithAndIvarScope.m”,参考一 下下面的代码 : 1 //legal, but not good 2 redBull->masterName = @"that cowgirl"; 3 //legal, but bad 4 //redBull->eyesCount = 3; 5 6 //Trying to access a private ivar, VERY bad thing 7 //MyClass bullClass = redBull->isa; 8 bool *redBullGender = (bool *)(redBull) + 8; 9 NSLog(@"My gender is %@",*redBullGender ?@"male" :@"female"); 在第二 行里面 ,我们访 问了 masterName,由于在 Cattle 里面 masterName 是public 的,Bull 继承 了 Cattle,所 以我们 可以直 接访问 masterName。但 是这不 是一种 好的习 惯,因 为这不 符合 面向对象 的基本思想。 实际上,如果 没有特殊的理 由,我们不需 要使用 public 的。 第 四行 ,我 们试 图在 类的 外边 访问 protected 变量eyesCount, 在这 里笔 者的 Xcode 只 是轻 轻的给了 一个警告 ,编译成功 并且可以运行 。同样 ,这种在类 的外边访问类 的 protected 变量是 一个很糟 糕的做法。 我们还记 得在 Bull 的saySomething 里面我们 曾经试图访问 过 gender,但是编译 器无情的阻 止了我们 ,因为 gender 是私有的 。但是 ,这仅仅是 编译器阻止了 我们 ,当我们有 足够的理由需 要在类的 外边访问 private 实例变量 的时候 ,我们还是 可以通过一些 强硬的方法合 法的访问私有 变量的, 我们的方法就 是使用指针偏 移。 我们首先 回忆一下第 6章的6.6 节的内容 ,isa 里面保存 了对象里面的 实例变量相对 于对象 首地址 的偏移量, 我们得到了 这个偏移量 之后就可以 根据对象的 地址来获得 我们所需要 的实例 变量的地 址。在正常情 况下,我们需 要通过访问类 本身和它的超 类的 ivars 来获得偏 移量的 ,但 是笔者 在这里偷了 一个懒 ,先使用 第七行的代 码 MyClass bullClass = redBull->isa;通过 Debugger 获得 gender 的偏移量 ,数值为 8。然后在第 8行里面 ,笔者通过 使用指针偏移 取得了 gender 的 指针然后 在第 9行实现了 输出。 由 此可 见, 在 Objective-C 里 面, 所 谓的 private 还有protected 只 是一 个 Objective-C 强烈 推荐的 一个规则, 我们需要按 照这个规则 来编写代码 ,但是如果 我们违反了 这个规则, 编译器 没有任何 方法阻止我们 。 笔者认为在类的外部直接访问任何实例变量,不管这个实例变量是public,private 还是 protected 都是 一个糟 糕的做 法,这 样会明 显的破 坏封装 的效果 ,尽管 这样对 编译器 来说是 合法 的。 7.4,initWith... NSObject 为我 们准 备的 不带任 何参 数的 init,我 们的 类里 面没有 实例 变量 ,或者 实例 变量 可以都 是零的时候 ,我们可以 使用 NSObject 为我们 准备的缺省 的 init。当我 们的实例变 量不能 为零 ,并且这些 实例变量的初 始值可以在类 的初始化的时 候就可以确定 的话 ,我们可以 重写 init, 并且在里 面为实例变量 初始化。 但 是在 很多 时候 , 我 们无 法预 测类 的初 始化 的时 候的 实例 变量 的初 始值 ,同 时 NSObject 明显无法 预测到我们需 要什么样的初 始值,所以我 们需要自己初 始化类的实例 变量。 请同学们 打开 “Cattle.m”,我们参 考一下下面的 代码: 1 -(id)init 2 { 3 [super init]; 4 return [self initWithLegsCount:4 5 gender:YES 6 eyesCount:2 7 masterName:@"somebody"]; 8 } 9 -(id)initWithLegsCount:(int) theLegsCount 10 gender:(bool) theGender 11 eyesCount:(int) theEyesCount 12 masterName:(NSString*)theMasterName13 { 14 legsCount = theLegsCount; 15 gender = theGender; 16 eyesCount = theEyesCount; 17 masterName = theMasterName; 18 return self; 19 } 从第 3行到 第 7行, 笔者重 写了一 下 init。在 init 里面 ,笔者 给通过 调用 initWith,给 类的各 个实例变 量加上了初始 值 。这样写是 很必要的 ,因为将来 的某个时候 ,也许有人 (或者是自 己 ) 很冒失的 使用 init 来初始化 对象 ,然后就尝 试使用这个对 象 ,如果我们 没有重写 init,那么也许 会出现一 些意想不到的 事情。 从第 9行到第 19 行,是我们自 己定义的 initWith,代码比较 简单 ,笔者就不 在这里赘述了 。 需 要注 意的 一点 是, 笔者 没有 在这 里调 用 [super init];。 原因 是 Cattle 的 超类 就是 NSObject,初 始化 的过 程就 是初始 化实 例变 量的过 程, runtime 已经 为我 们初 始化好 了 NSObject 的唯 一实 例 变量 isa,也 就是 Cattle 的类 的信息 ,所以 我们不 需要调 用 [super init];。在 某些时 候,超 类的变 量需要初 始化的时候, 请同学们在子 类的 init 或者 initWith 里面调用 [super init];。 请同学们 再次打开 “07-InitWithAndIvarScope.m”,参考下 面的代码片断 : 1 Bull *redBull = [[Bull alloc] initWithLegsCount:4 2 gender:YES 3 eyesCount:2 4 masterName:@"that cowboy"]; 5 [redBull setSkinColor:@"red"]; 6 [redBull saySomething]; 从第 1行到第 4行就是调 用的 initWith 来初始化 我们的 redBull。 7.5,本章总 结 非常感谢 大家对笔者的 支持! Objective-C 2.0 with Cocoa Foundation--- 8,类方法 以及私有方法 8,类方法 以及私有方法 本系列讲 座有着很强的 前后相关性 ,如果你是 第一次阅读本 篇文章 ,为了更好 的理解本章内 容 , 笔者建议 你最好从本系 列讲座的第 1章开始阅 读,请点击这 里。 Objective-C 里面区 别于实例方 法 ,和Java 或者 C++一样 ,也支持 类方法 。类方法 (Class Method) 有时被称 为工厂方法 (Factory Method)或者方便 方法 (Convenience method)。工厂方法 的称谓明显 和一般 意义上的工 厂方法不同 ,从本质上 来说,类方 法可以独立 于对象而执 行,所以在 其他的 语 言里 面类 方法 有的 时候 被称 为静 态方 法。 就像 @interface 曾 经给 我们 带来 的混 乱一 样, 现在 我们就不 去追究和争论 工厂方法的问 题了 ,我们看到 Objective-C 的文章说 工厂方法 ,就把它当 作类方法 好了。 在Objective-C 里面 ,最受 大家欢 迎的类 方法应 该是 alloc,我 们需要 使用 alloc 来为 我们的 对象 分配内存 。可以想象, 如果没有 alloc,我们将 要如何来为我 们的类分配内 存! 和其他的 语言类似,下 面是类方法的 一些规则,请 大家务必记住 。 1,类方法 可以调用类方 法。 2,类方法 不可以调用实 例方法,但是 类方法可以通 过创建对象来 访问实例方法 。 3,类方法 不可以使用实 例变量。类方 法可以使用 self,因为 self 不是实例 变量。 4,类方法作 为消息 ,可以被发 送到类或者对 象里面去 (实际上 ,就是可以 通过类或者对 象调用 类方法的 意思 )。 如果大家 观察一下 Cocoa 的类库 ,会发现类 方法被大量的 应用于方便的 对象创建和操 作对象的 , 考虑到 类方法的上 述的特性, 同学们在设 计自己的类 的时候,为 了谋求这种 方便,可以 考虑使 用类方法 来创建或者操 作对象 。笔者认为 ,这个就是 类方法的潜规 则 ,在本章的 范例程序里面 , 笔者将要 遵守这个潜规 则。 在上一 章我们讲了 一下实例变 量的作用域 ,实例变量 的作用域的 方式和其他 面向对象的 语言没 有什么不 同。对于方法 ,非常遗憾的 是, Objective-C 并没有为 我们提供诸如 public,private 和 protected 这样的限 定,这就意味 着在 Objective-C 里面,从 理论上来说所 有的方法都是 公有的 。 但是 ,我们可以 利用 Objective-C 的语言的 特性 ,我们自己 来实现方法的 私有化 。当然我们 自己 的私有化 手段没有得到 任何的编译器 的支持 ,只是告诉 使用者 :“这是一个 私有的方法 ,请不要 使用 这个方 法 ”。所 以,无 论作为 类的设 计者和 使用者 都应该 清楚在 Objective-C 里面 的方法 私 有化的所有手段,这样就在类的设计者和使用者之间达成了一种默契,这种方式明显不是 Objective-C 语法所硬 性规定的,所 以也可以把这 种手法成为一 种潜规则。 关于 潜规则 经常看 英文文 档的同 学,应 该可以 遇到这 样一个 词, de facto standard,也 就是笔 者 所说的潜 规则。 本章所 述的方法的 私有化是一 种有缺陷的 手段,有一 定的风险而 且也没有完 全实现私有 化,在 后面的章 节里面笔者会 陆续的给出其 他的实现方法 私有化的方法 。 另外, Objective-C 里面有一 个其他不支持 指针的语言没 有的一个动态 特性,那就是 程序在执行 的时候, 可以动态的替 换类的手段。 动态的方法替 换有很多种应 用,本章实现 了一个类似 java 里 面的 final 函 数。 和 final 函 数不 同的 是, 如果 子类 重写 了这 个方 法, 编译 器不 会报 错, 但是 执行的时 候总是执行的 你的超类的方 法。 类方法, 方法私有化和 动态方法替换 将是本章的主 题。 8.1,本章程 序的执行结果 在本章里 面,我们将要 继续使用我们 在第 4章已经构 筑好的类 Cattle 和Bull。 笔者在这 里暂时违反一 下不修改已经 生效的代码规 则改写了一下 Cattle 和Bull 类,在里 面追加 了一些类 方法,用于创 建 Cattle 系列的对 象。 笔者也改 写了 Cattle 的头文件 用来实现方法 的私有化。 面向对 象的程序有 一个很大的 特色就是动 态性,但是 由于某种原 因我们在设 计超类的时 候,也 许会考虑 把某个方法设 定成为静态的 ,这样就有 了诸如 final 的概念 。在本章我 们将要使用动 态 的方 法替换 来实现 这个功 能。我 们将要 构筑一 个新类 ,名字 叫做 UnknownBull,我 们使用 动态 方法替换导致即使UnknownBull 重载了Cattle 类的saySomething,但是向UnknownBull 发送 saySomething 的时候 ,仍然执行 的是 Cattle 的saySomething。本章程序 的执行结果请 参照下图 : 图8-1,本章程 序的执行结果 。 本章程序 可以点击这里 下载。 8.2,实现步 骤 第一步,按照我们在第2章所述的方法,新建一个项目,项目的名字叫做 07-InitWithAndIvarScope。如果你 是第一次看本 篇文章,请到 这里参看第二 章的内容。 第二 步, 按照 我们在 第 4章的 4.2 节的 第二 ,三 ,四步 所述 的方 法,把 在第 4章已 经使 用过 的 “Cattle.h”,“Cattle.m”,“Bull.h”还有 “Bull.m”,导入本章 的项目里面。 第三步, 打开 “Cattle.h”和“Cattle.m”,分别修 改成为下面的 代码并且保存 : #import @interface Cattle : NSObject { int legsCount; } -(void)saySomething; + (id) cattleWithLegsCountVersionA:(int) count; + (id) cattleWithLegsCountVersionB:(int) count; + (id) cattleWithLegsCountVersionC:(int) count; + (id) cattleWithLegsCountVersionD:(int) count;@end #import "Cattle.h" #import @implementation Cattle-(void) saySomething{ NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount);} -(void) setLegsCount:(int) count{ legsCount = count; } + (id) cattleWithLegsCountVersionA:(int) count{ id ret = [[Cattle alloc] init]; //NEVERDOLIKEBELOW //legsCount = count; [ret setLegsCount:count]; return [ret autorelease];} + (id) cattleWithLegsCountVersionB:(int) count{ id ret = [[[Cattle alloc] init] autorelease]; [ret setLegsCount:count]; return ret; } + (id) cattleWithLegsCountVersionC:(int) count{ id ret = [[self alloc] init]; [ret setLegsCount:count]; return [ret autorelease];} + (id) cattleWithLegsCountVersionD:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; if([self class] == [Cattle class]) return [ret autorelease]; SEL sayName = @selector(saySomething); Method unknownSubClassSaySomething = class_getInstanceMethod([self class], sayName); //Change the subclass method is RUDE! Method cattleSaySomething = class_getInstanceMethod([Cattle class], sayName); //method_imp is deprecated since 10.5 unknownSubClassSaySomething->method_imp = cattleSaySomething->method_imp; return [ret autorelease];} @end 第四步, 打开 “Bull.h”和“Bull.m”,分别修 改成为下面的 代码并且保存 : #import #import "Cattle.h"@interface Bull : Cattle { NSString *skinColor;} -(void)saySomething;- (NSString*) getSkinColor; -(void) setSkinColor:(NSString *) color; + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor;@end #import "Bull.h"@implementation Bull-(void) saySomething{ NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount);} -(NSString*) getSkinColor{ return skinColor; } -(void) setSkinColor:(NSString *) color{ skinColor = color; } + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor{ id ret = [self cattleWithLegsCountVersionC:count]; [ret setSkinColor:theColor]; //DONOTUSE autorelease here! return ret;} @end 第五步,创建一个新类,名字叫做“UnknownBull”,然后分别打开“UnknownBull.h”和 “UnknownBull.m”,分别修 改成为下面的 代码并且保存 : #import #import "Bull.h" @interface UnknownBull : Bull {} -(void)saySomething;@end #import "UnknownBull.h"@implementation UnknownBull -(void)saySomething{ NSLog(@"Hello, I am an unknown bull."); } @end 第六步, 打开 “08-Class_Method_And_Private_Method.m”,修改成 为下面的样子 并且保存 #import #import "Cattle.h"#import "Bull.h" #import "UnknownBull.h"int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle[5]; cattle[0] = [Cattle cattleWithLegsCountVersionA:4]; cattle[1] = [Bull cattleWithLegsCountVersionB:4]; cattle[2] = [Bull cattleWithLegsCountVersionC:4]; cattle[3] = [Bull bullWithLegsCount:4 bullSkinColor:@"red"]; cattle[4] = [UnknownBull cattleWithLegsCountVersionD:4]; for(int i = 0 ; i < 5 ; i++) { [cattle[i] saySomething]; } [pool drain]; return 0; } 第七步, 选择屏幕上方 菜单里面的 “Run”,然后选 择 “Console”,打开了 Console 对话框之 后 ,选 择对话框 上部中央的 “Build and Go”,如果不 出什么意外的 话,那么应该 出现入图 8-1 所示的结 果。如 果出现了什 么意外导致 错误的话, 那么请仔细 检查一下你 的代码。如 果经过仔细 检查发 现还是不 能执行的话 ,可以到这 里下载笔者 为同学们准 备的代码。 如果笔 者的代码还 是不能 执行的话 ,请告知笔者 。 8.2,方法的 私有化 在讲述方 法私有化之前 ,我们首先 要提到一个 Objective-C 里面的一 个概念 ,动态类型 和静态类 型。 所谓的动 态类型,就是 使用 id 来定义一 个对象,比如 说 id cattle = [[Cattle alloc] init]; 所谓的静 态类型,就是 使用已知变量 的的类型来定 义对象,比如 说 Cattle cattle = [[Cattle alloc] init]; 动态类 型和静态类 型各有好处 ,动态类型 实现了多态 性,使用静 态类型的时 候编译器会 为你检 查一下也 许会出现危险 的地方 ,比如说向 一个静态类型 的对象发送一 个它没有定义 的消息等等 。 好的 ,我们现在 打开 “cattle.h”,大家可以 发现 ,和以前的 版本相比 ,我们的 “cattle.h”少了一个 方 法的定义 ,那就是 -(void) setLegsCount:(int) count;。笔者在本 章的范例程序 里面实现私有 方法的 手段比较 简单,直接把 -(void) setLegsCount:(int) count 从“cattle.h”给删除掉 了。 大 家打 开 ““cattle.m”, 可以 看到 里 面 -(void) setLegsCount:(int) count 是 有实 现部 分 的。 实现 部分 和过去的 版本没有任何 区别的。 我们本 章里面讲述 的实现方法 私有化的手 段,就是从 头文件当中 不写方法的 声明。这样 做会导 致如下几 个现象 1,在 类的实 现文件 .m 里面 ,你可 以向平 常一样 使用 [self setLegsCount:4] 来发 送消息 ,但是 确 省设定的 编译器会很不 礼貌的给你一 个警告。 2,你可 以向 Cattle 以及从 Cattle 继承的 类的静态对 象发送 setLegsCount:4 的消息 ,但是同样 , 确省设定 的编译器会很 不礼貌的给你 一个警告。 3,你可 以向 Cattle 以及从 Cattle 继承的 类的动态对 象发送 setLegsCount:4 的消息 ,编译器不 会 向你发送 任何警告的。 说到这 里,同学们 也许会觉得 这一节的方 法私有化有 一点奇怪, 因为在上面 的第二条里 面,不 能阻止 对对象的私 有方法进行 调用。令我 们更为恼火 的是,居然 在我们自己 的类的实现 文件里 面需要调 用的时候产生 诸如第一条的 警告! 让我们冷 静一下。 我们说 ,在面向对 象的程序里 面,一般而 言类的使用 者只关心接 口,不关心 实现的。当 我们类 的实现 部分的某个 方法,在头 文件里面没 有定义的话 ,那么由于 我们的类的 使用者只是 看头文 件,所 以他不应该 是用我们定 义的所谓的 私有方法的 。这一点, 对于其他的 语言来说也 是一样 的, 其他 的语 言的私 有方 法和 变量, 如果 我们 把它们 改为 public,或 者我 们不 修改头 文件 ,使 用指针 也可以强行 的访问到私 有的变量和 方法的,从 这个角度上 来说,私有 化的方法和 变量也 只不过 是一个摆设 而已,没有 人可以阻止 我们去访问 他们,探求 埋藏在里面 的奥秘。所 谓的私 有化只 不过是一个 潜规则而已 ,在正常的 时候,我们 大家都会遵 守这个潜规 则的。但是 被逼无 奈走投 无路的时候 我们也许会 除了访问私 有的东西无 可选择。但 是也不能过 分,我们显 然不可 以把访问 私有变量和函 数当作一种乐 趣。 说到这 里,我想大 家应该可以 理解这种私 有化方法的 定义了。它 只不过是一 种信号,告 诉类的 使用者 ,“这是一个 私有的函数 ,请不要使 用它 ,否则后果 自负 ”。我们在看 到别人的代码 的时 候看到 了这种写法 的时候,或 者别人看到 我们的代码 的时候,大 家都需要做 到相互理解 对方的 隐藏私有 部分的意图。 还是还是这句 话,在大多数 时候,请不要 破坏潜规则。 8.3,类方法 我们现 在转到本章 最重要的主 题,类方法 。我们将要 首先关注一 下类方法的 声明,现在 请同学 们打开 "Cattle.h"文件,可 以发现下面的 代码: 1 + (id) cattleWithLegsCountVersionA:(int) count; 2 + (id) cattleWithLegsCountVersionB:(int) count; 3 + (id) cattleWithLegsCountVersionC:(int) count; 4 + (id) cattleWithLegsCountVersionD:(int) count; 类方法和 实例方法在声 明上的唯一的 区别就是 ,以加号 +为开始 ,其余的部 分是完全一致 的 。笔 者在 这里定 义了 4个不 同版本 的类方 法,从 功能上 来说都 是用来 返回 Cattle 类或 者其子 类的对 象的,其 中 cattleWithLegsCountVersionA 到C是我们这 一节讲解的重 点。 让我们首 先打开 “Cattle.m”,关注一 下下面的代码 : 1 + (id) cattleWithLegsCountVersionA:(int) count 2 { 3 id ret = [[Cattle alloc] init]; 4 //NEVERDOLIKEBELOW 5 //legsCount = count; 6 [ret setLegsCount:count]; 7 return [ret autorelease]; 8 } 9 + (id) cattleWithLegsCountVersionB:(int) count 10 { 11 id ret = [[[Cattle alloc] init] autorelease]; 12 [ret setLegsCount:count]; 13 return ret; 14 } 我们需 要使用类方 法创建对象 ,所以在第 3行,我 们使用了我 们比较熟悉 的对象的创 建的方法 创建了 一个对象。 大家注意一 下第 5行,由 于类方法是 和对象是脱 离的所以我 们是无法在 类方 法里面使 用实例变量的 。第6 行,由于我们 创建了对 象ret,所以我们 可以 向ret 发送setLegsCount: 这个消息 ,我们通过 这个消息 ,设定了 Cattle 的legsCount 实例变量 。在第 7行,我们遇到 了一 个新的朋 友, autorelease。我们在 类方法里面创 建了一个对象 ,当我们返回 了这个对象之 后 ,类 方法也随 之结束 ,类方法结 束就意味着在 我们写的类方 法里面 ,我们失去 了对这个对象 的参照 , 也就永远 无法在类方法 里面控制这个 对象了 。在Objective-C 里面有一 个规则 ,就是谁创 建的对 象,那 么谁就有负 责管理这个 对象的责任 ,类方法结 束之后,除 非和类的使 用者商量好 了让类 的使用者 释放内存,否 则我们无法直 接的控制这个 过程。 记忆力 好的同学应 该可以回忆 起来,笔者 曾经在第二 章提到过一 种延迟释放 内存的技术 ,这个 就是autorelease。 关于 autorelease 以 及其 他的 内存 管理 方法 ,我 们将 在下 一章 放到 一起 讲解 。 到这里 大家记住, 使用类方法 的潜规则是 你要使用类 方法操作对 象,当你需 要使用类方 法创建 一个对象 的时候,那么 请在类方法里 面加上 autorelease。 我们 来看看 cattleWithLegsCountVersionB 的实 现部分 的代码 ,和cattleWithLegsCountVersionA 唯 一 区别 就是 我们 在 创建 的时 候就 直 接的 加上 了 autorelease。 这样 符合 创建 对 象的 时候 “一 口气 ” 的把所有 需要的方法都 写到一起的习 惯,采取什么 方式取决于个 人喜好。 我们再打 开 “08-Class_Method_And_Private_Method.m”,参看下 面的代码 1 cattle[0] = [Cattle cattleWithLegsCountVersionA:4];2 cattle[1] = [Bull cattleWithLegsCountVersionB:4]; 我们 在回 头看 看本章 程序 的执 行结果 ,心 细的 同学也 许发 现了 一个很 严重 的问 题,我 们在 第 2 行代码里面想要返回一个Bull 的对象,但是输出的时候却变成了Cattle,原因就是我们在 cattleWithLegsCountVersionB 里面创建对象的时候,使用了id ret = [[[Cattle alloc] init] autorelease] 。由于Bull 里面没有重写cattleWithLegsCountVersionB ,所以除非我们重写 cattleWithLegsCountVersionB 否则 我们 向Bull 发送cattleWithLegsCountVersionB 这个 类方法 的时 候,只能得到一个Cattle 的对象。我们可以要求我们的子类的设计者在他们的子类当中重写 cattleWithLegsCountVersionB,但是这样 明显非常笨拙 ,失去了动 态的特性 。我们当然 有办法解 决这个问 题,现在请大 家回到 “Cattle.m”,参照下 列代码: 1 + (id) cattleWithLegsCountVersionC:(int) count 2 { 3 id ret = [[self alloc] init]; 4 [ret setLegsCount:count]; 5 return [ret autorelease]; 6 } 我们的解 决方案就在第 3行,我们不是 用静态的 Cattle,而是使用 self。说到这里 也许大家有些 糊涂了, 在其他的语言 当中和 self 比较类似 的是 this 指针,但 是在 Objective-C 里面 self 和this 有些不大 一样 ,在类函数 里面的 self 实际上就 是这个类本身 。大家可以 打开 debugger 观察一下 , self 的地址就 是 Bull 的Class 的地址 。所以程序 执行到上面的 代码的第 3行的时候 ,实际上就 等 同于 id ret = [[[Bull class] alloc] init]; 我们可以 在类方法里面 使用 self,我们可否 通过使用 self->legsCount 来访问实 例变量呢?答 案是 不可以 ,因为在这 个时候对象没 有被创建也就 是说 ,没有为 legsCount 分配内存 ,所以无法 访 问 legsCount。 由于 Bull 类在程序 被调入内存的 时候就已经初 始化好了 ,Bull 类里面的 实例函数应该 被放到了 代码段, 所以从理论上 来说,我们可 以通过使用 [self setLegsCount:count]来调用实 例方法的 ,但 是不幸的 是 Objective-C 没有允许 我们这样做 ,我们在类 方法中使用 self 来作为消 息的接收者的 时候, 消息总是被 翻译成为类 方法,如果 发送实例方 法的消息的 话,会在执 行的时候找 不到从 而产生 异常。这样 做是有一定 的道理的, 因为一般而 言,实例方 法里面难免 要使用实例 变量, 在类方法 当中允许使用 实例方法,实 际上也就允许 使用实例变量 。 关于 self 大家需要 记住下面的规 则: 1,实例方 法里面的 self,是对象 的首地址。 2,类方法 里面的 self,是 Class. 尽管在同 一个类里面的 使用 self,但是 self 却有着不 同的解读 。在类方法 里面的 self,可以翻译 成class self;在 实例方 法里面 的 self,应 该被翻 译成为 object self。在 类方法 里面的 self 和实 例 方法里面 的 self 有着本质 上的不同,尽 管他们的名字 都叫 self。 请同学们 再次回到图 8-1,可以发 现通过使用神 奇的 self,我们动 态的创建了 Bull 类的对象 。但 是等一下 ,我们的程 序并不完美 ,因为 Bull 类的 skinColor 并没有得 到初始化 ,所以导致 了 null 的出现。 我们在设计 Cattle 类也就是 Bull 的超类的 时候,明显我 们无法预测到 Bull 类的特征 。 消除 这种问 题,我 们可以 在得到 了 Bull 对象 之后使 用 setSkinColor:来设 定颜色 ,当然 我们也 可 以直接写 一个 Bull 类的方法 ,来封装这个 操作,请同学 们打开 “Bull.h”: + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor; 我 们追 加了 一个 类方 法, bullWithLegsCount:bullSkinColor:用 于创 建 Bull 对 象, 请同 学们 打开 “Bull.m”: 1 + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor 2 { 3 id ret = [self cattleWithLegsCountVersionC:count]; 4 [ret setSkinColor:theColor]; 5 //DONOTUSE autorelease here! 6 return ret; 7 } 上面这 一段代码相 信大家都可 以看明白, 笔者就不在 这里赘述了 。但是笔者 需要强调一 点,在 这里我们 不需要调用 autorelease 的,因为 我们没有在这 里创建任何对 象。 经过了这 个改造,通过 在 “08-Class_Method_And_Private_Method.m”里面我们 使用 cattle[3] = [Bull bullWithLegsCount:4 bullSkinColor:@"red"]; 使得我们 的代码终于正 常了,请参照 图 8-1 的第 4行输出。 8.4,使用动 态方法替换实 现 final 功能 首先请同 学们打开 “Cattle.m”,参照下 面的代码片断 : + (id) cattleWithLegsCountVersionD:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; if([self class] == [Cattle class]) return [ret autorelease]; SEL sayName = @selector(saySomething); Method unknownSubClassSaySomething = class_getInstanceMethod([self class], sayName); //Change the subclass method is RUDE! Method cattleSaySomething = class_getInstanceMethod([Cattle class], sayName); //method_imp is deprecated since 10.5 unknownSubClassSaySomething->method_imp = cattleSaySomething->method_imp; return [ret autorelease];}@end 在cattleWithLegsCountVersionD 里面 ,我们将要 通过使用动态 的方法替换技 术来实现 final 方法 。 第3,4行代码, 是用于创建 Cattle 或者从 Cattle 类继承的 对象,并且设 定实例变量 legsCount。 第6,7行代码 ,是用来判 断调用这个类 方法的 self 是不是 cattle,如果是 cattle 的话 ,那么就直 接返回, 因为我们要在 这个方法里面 把子 类的 saySomething 替换成为 Cattle 的saySomething, 如果 类是 Cattle 的话 ,那么 很明显 ,我们 不需要 做什么 事情的 。第 9行代 码是老 朋友了 ,我们 需要得到 方法的 SEL。 第10 行 和第 12 行 ,我 们需 要 通过 Objective-C 的 一个 底层 函 数, class_getInstanceMethod 来取 得方法的 数据结构 Method。让我们把 鼠标移动到 Method 关键字上 面 ,点击鼠标 右键盘 ,选择 “Jump to definition”, 我们 可以 看到 在 文件 “objc-class.h”里 面的 Method 的 定义 。 Method 实 际上 是类方法 在 Class 里面的数 据结构 ,系统会使 用 Method 的信息来 构筑 Class 的信息 。在Method 类型的声 明里面,我们 看到了下面的 代码 typedef struct objc_method *Method;struct objc_method { SEL method_name; char *method_types; IMP method_imp; }; 其中 SEL 和IMP我们已 经很熟悉了 ,method_types 是方法 的类型信息 ,Objective-C 使用一 些预 定义的宏 来表示方法的 类型,然后把 这些信息放到 method_types 里面。 需要强调 的是 ,苹果在 10.5 之后就降 级了很多 Objective-C 底层的函 数 ,并且在 64 位的应用 当 中使得这 些函数失效, 笔者对剥夺了 众多程序员的 自由而感到遗 憾。 第14 行的代码 ,我们把子 类的函数指针 的地址替换成 为 Cattle 类的 saySomething,这样无论 子 类是否 重写 saySomething,执行的 时候由于 runtime 需要找 到方法的入 口地址,但 是这个地址 总 是被 我们 替 换为 Cattle 的saySomething, 所以 子类 通 过 cattleWithLegsCountVersionD 取 得对 象之后, 总是调用的 Cattle 的saySomething,也就实 现了 final。当 然,这种 方法有些粗鲁 ,我 们强行的 不顾后果的替 换了子类的重 写。 重要本节 提到的 final 的实现方 法 ,没有任何 苹果官方的文 档建议这样做 ,纯属笔者 自创仅供大 家参考, 如果使用风险 自担。 替 换 的结 果 ,就 是 虽 然我 们 在 “08-Class_Method_And_Private_Method.m”里面的cattle[4]l 里面 使用 UnknownBull 是图返 回 UnknownBull 对象 ,我们也 确实得到了 UnknownBull 对象 ,但是不 同的是 ,我们在 cattleWithLegsCountVersionD 里面狸 猫换太子 ,把UnknownBull 的saySomething 变成了 Cattle 的saySomething。 让我们回 到图 8-1,我们发 现最后一行的 输出为 Cattle 的saySomething。 关于 final 的实现方 式 ,我们当然 可以使用一个 文明的方法来 告知子类的使 用者 ,我们不想 让某 个方法被 重写。我们只 需要定义一个 宏 #define FINAL 类的使用 者看到这个 FINAL 之后 ,笔者相信 在绝大多数时 候 ,他会很配 合你不会重写 带 FINAL 定义的方 法的。 8.5,本章总 结 我们在 本章里面讲 述了方法私 有化,类方 法的定义和 使用,动态 方法替换等 技术手段, 也给大 家强调和 澄清了 self 的概念 。更重要的 是 ,笔者向大 家介绍了一些 潜规则 ,希望大家 可以遵守 。 非常感谢 大家这些天对 我的鼓励以及 支持!
还剩40页未读

继续阅读

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

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

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

下载pdf

pdf贡献者

xyzs996

贡献于2012-05-31

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