原百度文库链接:http://wenku.baidu.com/view/6786064fe518964bcf847c63.htmlhtml
PS:须要原文档的能够留邮箱发送!java
(我叫雷锋,不要谢我)程序员
学习Objective-C入门教程express
1,前言编程
相信iPhone不久就要在国内发布了,和咱们在国内能够经过正规渠道买获得的iPodTouch同样,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,须要准备的东西
第一,你须要一台苹果电脑。固然这个不是必需的条件,若是你能够在你的IntelPC上成功安装MACOS的话,那么请忽略这一条。
第二,你须要去苹果网站上下载开发工具XCODE。注意,XCODE是彻底免费的,可是须要你去注册一个帐号才能够下载。因为XCODE不时的在更新,因此若是你的MACOS不支持你下载的XCODE的话,那么你也许须要考虑买一个最新的MACOS。
第三,你须要至少有C,C++,或者JAVA的背景知识。不过若是你没有,那么也不用担忧,相信阅读了笔者的文章以后应该也能够掌握。
最后须要的东西就不是必须的了,固然有的话会更好一些。这些东西是,开发者帐户(须要付费),iPhone手机(在部分国家能够免费得到,可是中国会怎么样,笔者不是很清楚),iPodTouch(须要购买)。
1.3,关于笔者的写做
笔者利用业余时间进行写做,因此没法对文章发布的时间表作出任何保证,还请各位读者谅解。可是笔者会尽最大努力在短期以内完成写做。
因为笔者经验才识所限,在本教程当中不免会遇到遗漏,错误甚至荒谬的地方,因此还请同窗们批评指正。
对于已经完成的章节,基于一些条件的改变或者勘误,或者你们提出的意见,笔者也会考虑作出适当的修改。
在每个章节都会有代码的范例,笔者注重阐述基本概念因此代码不免会有不完整或者错误的地方,同窗们能够任意的在本身的代码中使用笔者所写的代码,可是笔者不承担因为代码错误给同窗们带来的损失。同窗们在阅读本教程的时候,能够直接下载范例代码运行,可是为了熟悉编码的环境以及代码的规范,笔者强烈建议同窗们按照教程本身亲自输入代码。
Objective-C的概念比较多,并且不少概念都相互交叉。好比说讲解概念A的时候,须要概念B的知识,讲解概念B的时候须要概念C的知识,讲解概念C的时候须要概念A。这样就给本教程的写做带来了必定的麻烦,很明显笔者没法在某一个章节里面把全部的概念都讲述清楚,因此每一章都有侧重点,你们在阅读的时候须要抓住每一章的侧重点,忽略一些和本章内容无关的新的概念和知识。
1.4,本系列的结构
第1章,也就是本章
第2章,从Hello,World!开始
第3章,类的声明和定义
第4章,继承
第5章,Class类型,选择器Selector以及函数指针
第6章,NSObject的奥秘
第7章,对象的初始化以及实例变量的做用域
第8章,类方法以及私有方法
第9章,内存管理
第10章,到目前为止出现的内存泄漏事件
第11章,字符串,数组以及字典
第12章,属性
第13章,类目(Categories)
第14章,协议(Protocols)
第15章,Delegate
第16章,线程
第17章,文件系统
第18章,数据系列化以及保存用户数据
第19章,网络编程
第20章,XML解析
2,从Hello,World!开始
本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
如今笔者假设你们已经有了开发的环境。好了,咱们开始构筑咱们的第一个程序。
在开始第一个程序以前,笔者须要提醒你们一下,若是手里面有开发环境的话而且是第一次亲密接触Xcode的话,为了能够熟悉开发环境,强烈建议按照笔者的步骤一步一步的操做下去。
2.1,构筑Hello,World
第一步,启动Xcode。初次启动的时候,也许会弹出一个“WelcometoXcode”的一个对话框。这个对话框和咱们的主题没有关系,咱们能够把它关掉。
第二步,选择屏幕上部菜单的“File->NewProject”,出现了一个让你选择项目种类的对话框。你须要在对话框的左边选择“CommandLineUtility”,而后在右边选择“FoundationTool”,而后选择“Choose...”按钮。如图2.1所示。
图2-1,新建项目
注意也许有人会问,你不是要讲解iPhone的开发,那么为何不选择“iPhoneOS”下面的“Application”呢?
是这样的,在这个系列当中,笔者主要侧重于Objective-C的语法的讲解,为了使得讲解简单易懂,清除掉全部和要讲解的内容无关的东西,因此笔者在这里只是使用最简单的命令行。
第三步,Xcode会提问你项目的名字,在“SaveAs”里面输入“02-HelloWorld”,而后选择“Save”。如图2-2所示
图2-2,输入项目的名字
第四步,获得一个如图2-3所示的一个画面。尝试一下用鼠标分别点击左侧窗口栏里面的“02-HelloWorld”,“Source”.“Documentation”,“ExternalFrameworksandLibraries”,“Products”,而后观察一下右边的窗口都出现了什么东西。通常来讲,“02-HelloWorld”就是项目的名字下面是项目全部的文件的列表。项目下面的子目录分别是和这个项目相关的一些虚拟或者实际上的目录。为何我说是虚拟的呢?你们能够经过Finder打开你的工程文件的目录,你会发现你的全部文件竟然都在根目录下,根本就不存在什么“Source”之类的目录。
图2-3,项目浏览窗口
第五步,选择屏幕上方菜单的“Run”而后选择“Console”,出现了如图2-4所示的画面,用鼠标点击窗口中间的“BuildandGo”按钮。
图2-4,运行结果画面
若是不出什么意外的话,你们应该看到咱们熟悉得不能再熟悉的“HelloWolrd!”。因为咱们没有写任何的代码,因此从理论上来讲,这部分代码不该该出现编译错误。好的,从下面开始,笔者要开始对这个HelloWorld里面的一些新鲜的东西进行讲解。
2.2,头文件导入
在Java或者C/C++里面,当咱们的程序须要引用外部的类或者方法的时候,须要在程序源文件中包含外部的类以及方法的包(java里面的jarpackage)或者头文件(C/C++的.h),在Objective-C里面也有相相似的机制。笔者在这一节里面将要向你们介绍在Objective-C里面,头文件是怎样被包含进来的。
请同窗们到Xcode开发环境的左侧窗口里面,点击Source文件夹,而后就在右侧部分看到了代码源文件的列表,找到02-HelloWorld.m以后单击会在Xcode的窗口里面出现,双击鼠标代码会在一个新窗口出现,请同窗们按照这种方法打开"02-HelloWorld.m"。
对于Java程序来讲,源程序的后缀为.java,对于C/C++代码来讲,后缀为c/cpp,如今咱们遇到了.m。当Xcode看到了.m文件以后,就会把这个文件看成Objective-C文件来编译。同窗们也许会猜到,当Xcode遇到c/cpp,或者java的时候也会对应到相应的语言的。
好的,咱们顺便提了一下Xcode对.m文件的约定,如今咱们开始从第一行代码讲起,请参看下列代码:
1#import<Foundation/Foundation.h>
2
3intmain(intargc,constchar*argv[]){
4NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
5
6//insertcodehere
7NSLog(@"Hello,World!");
8[pooldrain];
9return0;
10}
11
有过C/C++经验的同窗看到第一行,也许会以为有些亲切;有过Java经验的同窗看到第一行也许也会有一种似曾相识的感受。同窗们也许猜到了这是干什么用的,没错,这个正是头文件。不过,在C/C++里面是#include,在java里面是import,这里是#import。
在C/C++里面会有#include互相包含的问题,这个时候须要#ifdef来进行编译的导向,在Xcode里面,同窗们能够"放心的"包含各类东西,这个没有关系,由于咱们的编译器有足够的“聪明”,由于同一个头文件只是被导入一次。除了#import变得聪明了一点以外,和#include的功能是彻底同样的。
咱们再来看看咱们的另一个新的朋友---Foundation.h。这个是系统框架Foundationframework的头文件,有了它你能够免费的获取系统或者说苹果公司为你精心准备的一系列方便你使用的系统功能,好比说字符串操做等等。Foundation框架从属于Cocoa框架集,Cocoa的另一个框架为ApplicationKit,或者是UIKit,其中前者的应用对象为MACOS,后者的应用对象为iPhoneOS。本系列入门指南将只是使用Foundation,由于笔者须要向同窗们介绍Objective-C的基本使用方法,为了不过多的新鲜东西给同窗们形成阅读上的困难,因此命令行就已经足够了。
说到这里,笔者须要澄清一点,其实MACOS的Cocoa和iPhone的Cocoa是不同的,能够说,其中iPhone是MACOS的一个子集。
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的一个软件,NeXTSoftware的缩写,NeXTSoftware是Cocoa的前身,一开始使用的是NS,为了保持兼容性因此NS一直得以保留。在多人开发的时候,为了不命名上的冲突,开发组的成员最好事先定义好各自的前缀。可是,最好不要有同窗使用NS前缀,这样会让其余人产生误解。
略微有些遗憾的是,Objective-C不支持namespace关键字,不知道后续的版本是否会支持。
下面咱们讨论一下Autorelease和Pool。
程序在执行的时候,须要向系统申请内存空间的,当内存空间再也不被使用的时候,毫无疑问内存须要被释放,不然有限的内存空间会很快被占用光光,后面的程序将没法获得执行的有效内存空间。从计算机技术诞生以来,无数的程序员,咱们的无数先辈都在为管理内存进行努力的工做,发展到如今,管理内存的工做已经获得了很是大的完善。
在Objective-C或者说Cocoa里面,有三种内存的管理方式。
第一种,叫作“GarbageCollection”。这种方式和java相似,在你的程序的执行过程当中,始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它何时开始工做,怎样工做。你只须要明白,我申请了一段内存空间,当我再也不使用从而这段内存成为垃圾的时候,我就完全的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人须要消耗必定的资源,在携带设备里面,资源是紧俏商品因此iPhone不支持这个功能。因此“GarbageCollection”不是本入门指南的范围,对“GarbageCollection”内部机制感兴趣的同窗能够参考一些其余的资料,不过说老实话“GarbageCollection”不大适合适初学者研究。
第二种,叫作“ReferenceCounted”。就是说,从一段内存被申请以后,就存在一个变量用于保存这段内存被使用的次数,咱们暂时把它称为计数器,当计数器变为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能够明显减低程序复杂度和易读性的时候,仍是考虑使用一下换一下口味。
说到这里,可能有的同窗已经开始发晕了,认为这个东西比较难以理解。是的,笔者在这里只是介绍一个大概的东西,在这里只要了解计数器的概念就能够了,笔者将在随后的章节里面对这个功能加以详细论述,请同窗们放心,这个东西和HelloWorld同样简单。
关于Pool
在使用Pool的时候,也要记住系统给你的Pool的容量不是无限大的,从这一点来讲和在现实世界的信用卡比较类似。
你能够在必定程度透支,可是若是“忘记掉”信用卡的额度的话,会形成很大的系统风险。
第三种,就是传统而又原始的C语言的方式,笔者就不在这里叙述了。除非你在Objective-C里面使用C代码,不然不要使用C的方式来申请和释放内存,这样会增长程序的复杂度。
线程是什么东西?线程指的是进程中一个单一顺序的控制流。它是系统独立调度和分派的基本单位。同一进程中的多个线程将共享该进程中的所有系统资源,好比文件描述符和信号处理等等。一个进程能够有不少线程,每一个线程并行执行不一样的任务。
2.5,关于[[NSAutoreleasePoolalloc]init];
关于程序第4行等号右边出现的括弧以及括弧里面的内容,笔者将在后续的章节里面介绍。在这里,同窗们能够理解为,经过告诉Objective-C编译器[[NSAutoreleasePoolalloc]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的注释的写法,以及命令行的输出方法。
是否是很简单又颇有乐趣呢?笔者将会尽最大努力把看起来复杂的东西讲解的简单一些,而且真心的但愿你们能够从中找到乐趣。
3,类的声明和定义
本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
上一章咱们写了一个很是简单的Obejctive-C下面的Hello,World!的小程序,而且对里面出现的一些新的概念进行了解释。这一章,咱们将要深刻到Objective-C的一个基本的要素,也就是类的声明和定义。经过本章的学习,同窗们应该能够定义类,给类加上变量,还有经过方法访问类的变量。不过准确的说,变量和方法的名词在Objective-C里面并非最准确的称呼,咱们暂时引用Java的定义,稍后咱们将统一咱们的用语定义。
3.1,本章的程序的执行结果。
咱们将构筑一个类,类的名字叫作Cattle,也就是牛的意思,今年是牛年并且我还想给在股市奋战的同窗们一个好的名字,因此咱们暂时把这个类叫作牛类。
咱们在main里面初始化这个牛类,而后调用这个类的方法设定类的变量,最后调用这个类的一个方法,在屏幕上输出,最终输出的结果以下图3-1所示
图3-1,牛类的输出结果
完整的代码在这里。不过为了熟悉编辑环境以及代码,笔者强烈建议同窗们按照下面的步骤本身输入。
3.2,实现步骤
第一步,按照咱们在第二章所述的方法,新建一个项目,项目的名字叫作03-HelloClass。固然,你也能够起一个别的更好听的名字,好比说HelloCattle等等,这个并不妨碍咱们的讲解。若是你是第一次看本系列文章,请到这里参看第二章的内容。
第二步,把鼠标移动到左侧的窗口的“Source”目录,而后单击鼠标右键,选择“Add”,而后界面上会出来一个子菜单,在子菜单里面选择“NewFile...”。如图3-2所示:
图3-2,新建文件
第三步,在新建文件对话框的左侧选择“CocoaTouchClasses”,而后在右侧窗口选择“NSObjectsubclass”,而后单击“Next”。如图3-3所示:
第四步,在“NewFile”对话框里面的“FileName”栏内输入“Cattle.m”。注意,在确省状态下,Xcode为你加上了“.m”的后缀,这个也是编译器识别Objective-C源文件的方法,没有特殊理由请不要修改这个后缀,不然会让编译器感到不舒服。另外请确认文件名字输入栏的下方有一个“Alsocreate"Cattel.h"”选择框,请保持这个选择框为选择的状态。如图3-4所示。
第5步,在项目浏览器里面选择“Cattle.h”文件,把文件改成以下代码而且保存(Command键+S):
#import<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
}
-(void)saySomething;
-(void)setLegsCount:(int)count;
@end
为何legsCattle者,牛也;legs者,股也。不过牛股里面的牛正确的英文说法应该是Bull,请你们不要着急,咱们会在类的继承里面命名一个Bull类的。
第六步,在项目浏览器里面选择“Cattle.m”文件,把文件改成以下代码而且保存(Command键+S):
#import"Cattle.h"
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
@end
第七步,在项目浏览器里面选择“03-HelloClass.m”文件,把文件改成以下代码而且保存(Command键+S):
#import<Foundation/Foundation.h>
#import"Cattle.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
[cattlesetLegsCount:4];
[cattlesaySomething];
[pooldrain];
return0;
}
第八步,选择屏幕上方菜单里面的“Run”,而后选择“Console”,打开了Console对话框以后,选择对话框上部中央的“BuildandGo”,若是不出什么意外的话,那么应该出现入图3-1所示的结果。若是出现了什么意外致使错误的话,那么请仔细检查一下你的代码。若是通过仔细检查发现仍是不能执行的话,能够到这里下载笔者为同窗们准备的代码。若是笔者的代码仍是不能执行的话,请告知笔者。
3.3,类的声明
从Objective-C名字咱们就能够得知,这是一个面向对象的语言。面向对象的一个最基础的要素就是类的概念,Objective-C也不例外。所谓的类的概念,实际上是从C语言的结构体发展而来的。咱们知道,C语言里面的结构体仅仅有数据的概念,面向对象的语言不只仅支持数据,还能够在结构体里面封装用于存取结构体数据的方法。结构体的数据和方法结合,咱们把整个结构体称为类(Class)。仅仅有了类,是不能执行任何操做的,咱们必须把类进行实体化,实体化后的类咱们称之为对象(Object)。从这个角度上来讲,咱们能够认为类是对象的模版。
若是要使用类,那么和构造体相相似,咱们必须声明这个类。
请参照“Cattle.h”文件:
1#import<Foundation/Foundation.h>
2
3
4@interfaceCattle:NSObject{
5intlegsCount;
6}
7-(void)saySomething;
8-(void)setLegsCount:(int)count;
9@end
若是看过本系列第二章的同窗们,第一行应该是一个老面孔了,咱们知道咱们须要这个东西免费得到苹果公司为咱们精心准备的FoundationFramework里面的不少的功能。若是不使用这个东西的话,咱们的工做将会很复杂。
同窗们请看第4行和第9行的第一个字母,又出现了“@”符号。为何说又呢,由于咱们在第二章的字符串前面也看到过这个东西。字符串前面出现这个符号是由于咱们须要和C语言的字符串定义区别开来,咱们须要编译器导向。在这里,我要告诉同窗们的是,这里的“@”符号的做用仍是一样是编译器导向。咱们知道Java和C++定义了一个关键字class用于声明一个类,在Objective-C里面,不存在这样的关键字。在Objective-C里面,类的定义从@interface开始到@end结束,也就是说,编译器看到了@interface就知道了这是类的定义的开始,看到了@end就知道,类的定义结束了。
咱们这里类的名字是“Cattle”,咱们使用了空格和@interface分开,通知编译器,咱们要声明一个类,名字叫作Cattle。在Cattle的后面,咱们有“:NSObject”,这是在通知编译器咱们的Cattle是从NSObject继承而来的,关于继承和NSObject,咱们将在后面的章节里面详细介绍,关于“:NSObject”咱们如今能够理解为,经过这样写,咱们免费得到了苹果公司为咱们精心准备的一系列的类和对象的必备的方法。NSObject被称为rootclass,也就是根类。在Java或者.NET里面,根类是必备的,C++不须要。在Obejctive-C里面原则上,你能够不使用NSObject,构筑一个你本身的根类,可是事实上这样作将会有很大工做量,并且这样作没有什么意义,由于苹果为你提供的NSObject通过了很长时间的检验。也许有好奇心的同窗们想本身构筑根类,不过至少笔者不会有本身去构筑一个根类的欲望。
好的,你们如今来看第5行。咱们之前把这个东西叫作变量,咱们从如今开始,须要精确的使用Objective-C的用语了,这是实体变量(instancevariables,在有的英文资料里面会简写为iVars)。虽然做为一个Cattle,它有不止一个实体变量,好比说体重等等,可是为了代码简洁,咱们在这里声明一个就是牛腿也就是牛股的数目,这个实体变量是int型,表示一个整数,咱们固然不但愿有4.5个牛腿。
咱们来看第6行,第6行的括弧和在第4行最后的括弧用来表示实体变量的定义区间,编译器认为在这两个括弧之间的定义是实体变量的定义。固然,若是你的类没有实体变量,那么这两个括弧之间容许什么都没有。和Java以及C++不同,Objective-C要求在括弧里面不能有方法也就是函数的定义,那么Objective-C里面的方法的定义放在什么地方呢,请看第7行。
第7行的第一个字母是一个减号“-”。这个减号就是告诉编译器,减号后面的方法,是实体方法(instancemethod)。实体方法的意思就是说,这个方法在类没有被实体化以前,是不能运行的。咱们在这里看到的是减号,在有减号的同时也有加号,咱们把带加号的方法称为类方法(classmethod),和实体方法相对应,类方法能够脱离实体而运行。关于类方法,咱们将在后面的章节里面讲解。你们也许能够想起来在C++和Java里面一样也有相似的区分,不是么。
在Objective-C里面方法的返回类型须要用圆括号包住,当编译器看到减号或者加号后面的括号了以后,就会认为这是在声明方法的返回值。你也能够不声明返回值,Objective-C的编译器会给没有写显式的返回值函数加上一个默认的返回值,它的类型是id,关于id类型咱们将在后面讲解,不过笔者不推荐不写返回值的类型。
在第7行咱们定义了这个方法的名字是saySomething,固然Cattle说的话咱们人类是听不懂的,笔者只是想让它在咱们的控制台里面输出一些咱们能够看得懂得字符串。方法的声明最后,须要分号来标识,这一点保持了和C没有任何区别。
咱们再来看看第8行,第8行和第7行多了“:(int)count”。其中冒号放在方法的后面是用来表示后面是用来定义变量的,一样变量的类型使用括号给包住,若是不写变量的类型的化,编译器一样认为这是一个id类型的。最后的count,就是变量的名字。若是有不仅一个变量怎么办?答案就是在第一个变量后面加冒号,而后加园括号包住变量的类型,接着是变量的名字。
好了,咱们在这里总结一下,类的定义方法以下:
@interface类的名字:父类的名字{
实体变量类型实体变量名字;
}
-(返回值类型)方法名字;
+(返回值类型)方法名字;
-(返回值类型)方法名字:(变量类型)变量名字标签1:(变量类型)变量1名字;
@end
...的意思在本系列入门讲座里面,...表示省略了一些代码的意思。
3.4,类的定义
咱们在前一节讲述了类的声明,咱们下一步将要看一下类的定义。请同窗们打开“Cattle.m”文件:
1#import"Cattle.h"
2
3
4@implementationCattle
5-(void)saySomething
6{
7NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
8}
9-(void)setLegsCount:(int)count
10{
11legsCount=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<Foundation/Foundation.h>
2#import"Cattle.h"
3
4intmain(intargc,constchar*argv[]){
5NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
6
7idcattle=[Cattlenew];
8[cattlesetLegsCount:4];
9[cattlesaySomething];
10
11[pooldrain];
12return0;
13}
同窗们请看第7行的第一个单词id。id是英文identifier的缩写,咱们在不少地方都遇到过id,好比说在博客园里面,咱们都使用id来登录系统的,咱们的id就表明着系统的一个用户。因为id在一个系统当中是惟一的,因此系统得到咱们的id以后就知道咱们是谁了。Objective-C也是同样的道理,使用id来表明一个对象,在Objective-C当中,全部的对象均可以使用id来进行区分。咱们知道一个类仅仅是一些数据外加上操做这些数据的代码,因此id其实是指向数据结构的一个指针而已,至关于void*。
第7行的第二个单词是cattle,就是咱们给这个id起的一个名字。固然,你能够起系统保留的名字之外的任何名字,不过为了维持代码的可读性,咱们须要一个有意义的名字,咱们这里使用头文字为小写的cattle。
第7行的[Cattlenew]是建立对象,new其实是alloc和init的组合,在Objective-C里面建立对象是一个为对象分配内存和初始化的过程。new,alloc还有init定义在Cattle的超类NSObject里面,笔者将要在第7章里面详细的解释一下如何建立对象。在第7章以前咱们都是用new来建立对象。
Objective-C里面的方法的使用和其余语言有些不一样,Objective-C使用消息(Message)来调用方法。因此笔者认为在讲解第7行等号右边的部分以前,须要首先向你们介绍一个咱们的新朋友,消息(Message)。所谓的消息就是一个类或者对象能够执行的动做。消息的格式以下:
[对象或者类名字方法名字:参数序列];
首先咱们观察到有两个中括弧,最右边的括弧以后是一个分号,当编译器遇到了这个格式以后会把中间的部分看成一个消息来发送。在上文的表达式当中,包括中括弧的全部部分的内容被称做消息表达式(Messageexpression),“对象或者类名字”被称做接收器(Receiver),也就是消息的接受者,“方法名字:参数序列”被称为一个消息(Message),“方法名字”被称做选择器(Selector)或者关键字(Keyword)。Objective-C和C语言是彻底兼容的,C语言里面的中括弧用于表示数组,可是数组的格式明显和消息的发送的格式是不同的,因此咱们能够放心,编译器不会把咱们的消息发送看成一个数组。
咱们来回忆一下C语言里面函数的调用过程,实际上编译器在编译的时候就已经把函数相对于整个执行包的入口地址给肯定好了,函数的执行实际上就是直接从这个地址开始执行的。Objective-C使用的是一种间接的方式,Objective-C向对象或者类(具体上是对象仍是类的名字取决于方法是实体方法仍是类方法)发送消息,消息的格式应该和方法相同。具体来讲,第7行等号右边的部分[Cattlenew]就是说,向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的奇妙做用
- 如何从类开始初始化对象
- 消息的调用
4,继承
本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
上一章笔者介绍了一下在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-HelloInheritance。若是你是第一次看本篇文章,请到这里参看第二章的内容。
第二步,把鼠标移动到项目浏览器上面的“Source”上面,而后在弹出的菜单上面选择“Add”,而后在子菜单里面选择“ExsitingFiles”,如图4-2所示
图4-2,向项目追加文件
第三步,在文件选择菜单里面,选择第3章的项目文件夹“03-HelloClass”,打开这个文件夹以后,用鼠标和苹果电脑的COMMAND键,选泽文件“Cattle.h”和“Cattle.m”,而后按下“Add”按钮,如图4-3所示。若是你没有下载第3章的代码,请点击这里下载。
图4-3,选择文件
第四步,在追加文件的选项对话框里面,让“Copyitemsintodestinationgroup'sfolder(ifneeded)”的单选框变为被选择的状态。这样就保证了咱们在第三步里面选择的文件被拷贝到了本章的项目里面,能够避免咱们不当心更改“Cattle.h”和“Cattle.m”对已经生效的第3章程序产生影响,虽然咱们在本章里面不更改这2个代码。
第五步,把鼠标移动到项目浏览器上面的“Source”上面,而后在弹出的菜单上面选择“Add”,而后在子菜单里面选择“NewFiles”,而后在新建文件对话框的左侧选择“CocoaTouchClasses”,而后在右侧窗口选择“NSObjectsubclass”,选择“Next”,在“NewFile”对话框里面的“FileName”栏内输入“Bull.m”。在这里笔者没有给出图例,在这里新建文件的步骤和第3章的第二步到第四步相同,只是文件名字不同。第一次看到本篇文章的同窗能够参照第3章。
第六步,打开Bull.h作出以下修改,而且保存。
#import<Foundation/Foundation.h>
#import"Cattle.h"
@interfaceBull:Cattle{
NSString*skinColor;
}
-(void)saySomething;
-(NSString*)getSkinColor;
-(void)setSkinColor:(NSString*)color;
@end
第七步,打开Bull.m作出以下修改,而且保存
#import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
@end
第八步,打开04-HelloInheritance.m文件,作出以下修改,而且保存
#import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
[cattlesetLegsCount:4];
[cattlesaySomething];
idredBull=[Bullnew];
[redBullsetLegsCount:4];
[redBullsetSkinColor:@"red"];
[redBullsaySomething];
Bull*blackBull=[Bullnew];
[blackBullsetLegsCount:4];
[blackBullsetSkinColor:@"black"];
[blackBullsaySomething];
[pooldrain];
return0;
}
第九步,选择屏幕上方菜单里面的“Run”,而后选择“Console”,打开了Console对话框以后,选择对话框上部中央的“BuildandGo”,若是不出什么意外的话,那么应该出现入图4-1所示的结果。若是出现了什么意外致使错误的话,那么请仔细检查一下你的代码。若是通过仔细检查发现仍是不能执行的话,能够到这里下载笔者为同窗们准备的代码。若是笔者的代码仍是不能执行的话,请告知笔者。
4.3,子类Subclass和超类Superclass
让咱们首先回忆一下第3章的Cattle.h,在Cattle.h里面咱们有以下的代码片段:
@interfaceCattle:NSObject{
这段代码是在告诉编译器,咱们的Cattle是继承的NSObject。在这段代码当中,NSObject是超类,Cattle是子类。经过这样写,咱们曾经免费的获得了NSObject里面的一个方法叫作new。
idcattle=[Cattlenew];
在面向对象的程序设计当中,若是在子类当中继承了超类的话,那么超类当中已经生效的部分代码在子类当中仍然是有效的,这样就大大的提升了代码的效率。基于超类咱们能够把咱们须要追加的一些功能放到子类里面去,在本章里面,咱们决定基于Cattle类,从新生成一个子类Bull:
1#import<Foundation/Foundation.h>
2#import"Cattle.h"
3
4@interfaceBull:Cattle{
5NSString*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,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
咱们在这句话当中,发现的第一个新朋友是%@,这是在告诉编译器,须要把%@用一个后面定义的字符串来替换,在这里咱们给编译器提供的字符串是[selfgetSkinColor]。看到这里,同窗们又会发现一个新的朋友self。
在类的方法定义域以内,咱们有的时候须要访问这个类本身的实例变量,或者是方法。在类被实例化以后,咱们就能够使用一个指向这个类自己的一个指针,在Java或者C++里面的名字叫作this,在Objective-C里面,这个名字是self。self自己是一个id类型的一个指针变量。咱们在第3章里面讲解过,方法的调用格式以下:
[对象或者类名字方法名字:参数序列];
在类的方法定义域里面,当咱们须要调用类的其余方法的时候,咱们须要指定对象或者类的名字,咱们的方法是一个实例方法因此咱们须要一个指向本身的对象,在这里咱们须要使用self。
咱们假设,若是方法声明里面的参数序列里面有一个参数的名字和类的实例变量发生重复的状况下而且因为某种缘由咱们没法更改参数和实体变量的名字的话,咱们应该如何应对呢?答案是使用self,格式以下
self->变量名字
经过这样写,咱们能够取获得类的变量的数值。固然若是没有名字冲突的话,咱们彻底能够省略self->,Xcode也足够的聪明可以识别咱们的实例变量,而且把咱们代码里面的实例变量更改成相应的醒目的颜色。
若是咱们在类的方法里面须要访问超类的方法或者变量(固然是访问对子类来讲是可视的方法或者变量),咱们须要怎样写呢?答案是使用super,super在本质上也是id的指针,因此,使用super访问变量和方法的时候的书写格式,和self是彻底同样的。
“Bull.m”里面的其余的代码,没有什么新鲜的东西,因此笔者就不在这里赘述了。
4.5,超类方法和子类方法的执行
咱们来看一下04-HelloInheritance.m的下面的代码片段
1idredBull=[Bullnew];
2[redBullsetLegsCount:4];
3[redBullsetSkinColor:@"red"];
4[redBullsaySomething];
5
6Bull*blackBull=[Bullnew];
7[blackBullsetLegsCount:4];
8[blackBullsetSkinColor:@"black"];
9[blackBullsaySomething];
第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=[Bullnew];
4.6,本章总结
感谢你们阅读到这里!咱们在本章学习了:
- 超类,子类的概念以及如何定义和声明。
- self和super的使用方法以及使用的时机。
- 超类和子类的方法的执行。
5,Class类型,选择器Selector以及指针函数
- 本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
- 上一章笔者介绍了在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和CocoaFoundation的特性。
- 好了,说了不少咱们从下面就要开始,咱们的手法和前面几章是同样的,咱们首先要介绍一下本章程序的执行结果。
5.1,本章程序的执行结果
- 图5-1,第5章程序的执行结果
- 在本章里面,咱们将要继续使用咱们在前面几章已经构筑好的类Cattle和Bull。为了灵活的使用Cattle和Bull,咱们将要构筑一个新的类,DoProxy。在DoProxy里面,咱们将会引入几个咱们的新朋友,他们分别是BOOL,SEL,IMP,CLASS。经过这些新的朋友咱们能够动态的经过设定文件取得Cattle和Bull的类,还有方法以及方法指针。下面将要介绍如何构筑本章程序。同窗们能够按照本章所述的步骤来构筑,也能够经过从这里下载。不过为了熟悉代码的写做,笔者强烈建议你们按照笔者所述的步骤来操做。
5.2,实现步骤
- 第一步,按照咱们在第2章所述的方法,新建一个项目,项目的名字叫作05-HelloSelector。若是你是第一次看本篇文章,请到这里参看第二章的内容。
- 第二步,按照咱们在第4章的4.2节的第二,三,四步所述的方法,把在第4章已经使用过的“Cattle.h”,“Cattle.m”,“Bull.h”还有“Bull.m”导入本章的项目里面。若是你没有第4章的代码,请到这里下载。若是你没有阅读第4章的内容,请参看这里。
- 第三步,把鼠标移动到项目浏览器上面的“Source”上面,而后在弹出的菜单上面选择“Add”,而后在子菜单里面选择“NewFiles”,而后在新建文件对话框的左侧选择“CocoaTouchClasses”,而后在右侧窗口选择“NSObjectsubclass”,选择“Next”,在“NewFile”对话框里面的“FileName”栏内输入“DoProxy.m”。在这里笔者没有给出图例,在这里新建文件的步骤和第3章的第二步到第四步相同,只是文件名字不同。第一次看到本篇文章的同窗能够参照第3章。
- 第四步,打开“DoProxy.h”作出以下修改而且保存
- #import<Foundation/Foundation.h>
#defineSET_SKIN_COLOR@"setSkinColor:"
#defineBULL_CLASS@"Bull"
#defineCATTLE_CLASS@"Cattle"
@interfaceDoProxy:NSObject{
BOOLnotFirstRun;
idcattle[3];
SELsay;
SELskin;
void(*setSkinColor_Func)(id,SEL,NSString*);
IMPsay_Func;
ClassbullClass;
}
-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color;
-(void)setAllIVars;
-(void)SELFuncs;
-(void)functionPointers;
@end
- 第五步,打开“DoProxy.m”作出以下修改而且保存
- #import"DoProxy.h"
#import"Cattle.h"
#import"Bull.h"
@implementationDoProxy
-(void)setAllIVars
{
cattle[0]=[Cattlenew];
bullClass=NSClassFromString(BULL_CLASS);
cattle[1]=[bullClassnew];
cattle[2]=[bullClassnew];
say=@selector(saySomething);
skin=NSSelectorFromString(SET_SKIN_COLOR);
}
-(void)SELFuncs
{
[selfdoWithCattleId:cattle[0]colorParam:@"brown"];
[selfdoWithCattleId:cattle[1]colorParam:@"red"];
[selfdoWithCattleId:cattle[2]colorParam:@"black"];
[selfdoWithCattleId:selfcolorParam:@"haha"];
}
-(void)functionPointers
{
setSkinColor_Func=(void(*)(id,SEL,NSString*))[cattle[1]methodForSelector:skin];
//IMPsetSkinColor_Func=[cattle[1]methodForSelector:skin];
say_Func=[cattle[1]methodForSelector:say];
setSkinColor_Func(cattle[1],skin,@"verbose");
NSLog(@"Runningasafunctionpointerwillbemoreefficiency!");
say_Func(cattle[1],say);
}
-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color
{
if(notFirstRun==NO)
{
NSString*myName=NSStringFromSelector(_cmd);
NSLog(@"Runninginthemethodof%@",myName);
notFirstRun=YES;
}
NSString*cattleParamClassName=[aCattleclassName];
if([cattleParamClassNameisEqualToString:BULL_CLASS]||
[cattleParamClassNameisEqualToString:CATTLE_CLASS])
{
[aCattlesetLegsCount:4];
if([aCattlerespondsToSelector:skin])
{
[aCattleperformSelector:skinwithObject:color];
}
else
{
NSLog(@"Hi,Iama%@,havenotsetSkinColor!",cattleParamClassName);
}
[aCattleperformSelector:say];
}
else
{
NSString*yourClassName=[aCattleclassName];
NSLog(@"Hi,youarea%@,butIlikecattleorbull!",yourClassName);
}
}
@end
- 第六步,打开“05-HelloSelector.m”做出以下修改而且保存
- #import<Foundation/Foundation.h>
#import"DoProxy.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
DoProxy*doProxy=[DoProxynew];
[doProxysetAllIVars];
[doProxySELFuncs];
[doProxyfunctionPointers];
[pooldrain];
return0;
}
- 第七步,选择屏幕上方菜单里面的“Run”,而后选择“Console”,打开了Console对话框以后,选择对话框上部中央的“BuildandGo”,若是不出什么意外的话,那么应该出现入图5-1所示的结果。若是出现了什么意外致使错误的话,那么请仔细检查一下你的代码。若是通过仔细检查发现仍是不能执行的话,能够到这里下载笔者为同窗们准备的代码。若是笔者的代码仍是不能执行的话,请告知笔者。
5.3,BOOL类型
- 咱们如今打开“DoProxy.h”文件。“DoProxy.h”文件的第3行到第5行是三个预约义的三个字符串的宏。咱们将在程序当中使用这3个宏,为了实现代码的独立性,在实际的程序开发当中,咱们也许考虑使用一个配置的文本文件或者一个XML来替代这些宏。可是如今因为笔者的主要目的是讲解Objective-C的概念,为了不较多的代码给你们带来理解主题的困难,因此笔者没有使用配置文件或者XML来表述这些能够设定的常量。
- “DoProxy.h”的第7行对同窗们来讲也是老朋友了,是通知编译器,咱们须要声明一个DoProxy类,从NSObject继承。
- 咱们在第8行遇到了咱们的一个新的朋友,BOOL:
- BOOLnotFirstRun;
- 咱们定义了一个notFirstRun的实例变量,这个变量是布尔类型的。咱们的实例方法doWithCattleId须要被执行屡次,咱们在第一次执行doWithCattleId的时候须要向控制输出包含doWithCattleId的方法名字的字符串,关于这个字符串的内容,请参考图5-1。
- 好的,咱们如今须要看看在Objective-C里面BOOL是怎样定义的,咱们把鼠标移动到BOOL上面,而后单击鼠标右键选择弹出菜单的“JumptoDefinition”,而后Xcode会打开objc.h文件,咱们看到下面的代码:
- typedefsignedcharBOOL;
//BOOLisexplicitlysignedso@encode(BOOL)=="c"ratherthan"C"
//evenif-funsigned-charisused.
#defineOBJC_BOOL_DEFINED
#defineYES(BOOL)1
#defineNO(BOOL)0
- 咱们看到这段代码,咱们能够这样理解,在Objective-C里面,BOOL实际上是signedchar,YES是1,NO是0。咱们能够这样给BOOL赋值:
- BOOLx=YES;
BOOLy=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”文件的下列代码:
- 1idcattle[3];
2SELsay;
3SELskin;
- 其中idcattle[3]定义了一个数组用于存储Cattle或者Bull对象。这一行代码估计你们都很熟悉,笔者就不赘述了。像这样的传统的数组并不能彻底知足咱们的需求,当咱们须要作诸如追加,删除等操做的时候,会很不方便。在随后的章节里面笔者将要向你们介绍传统数组的替代解决方案NSArray。
- 上一段代码的第二行和第三行是本节所关注的,就是SEL类型。Objective-C在编译的时候,会根据方法的名字(包括参数序列),生成一个用来区分这个方法的惟一的一个ID,这个ID就是SEL类型的。咱们须要注意的是,只要方法的名字(包括参数序列)相同,那么它们的ID都是相同的。就是说,无论是超类仍是子类,无论是有没有超类和子类的关系,只要名字相同那么ID就是同样的。除了函数名字和ID,编译器固然还要把方法编译成为机器能够执行的代码,这样,在一个编译好的类里面,就产生了以下图所示方法的表格示意图(本构造属于笔者推测,没有获得官方证明,因此图5-2为示意图仅供参考,咱们能够暂时认为是这样的)。
- 图5-2,方法的表格示意图
- 请注意setSkinColor后面有一个冒号,由于它是带参数的。因为存在这样的一个表格,因此在程序执行的时候,咱们能够方便的经过方法的名字,获取到方法的ID也就是咱们所说的SEL,反之亦然。具体的使用方法以下:
- 1SEL变量名=@selector(方法名字);
2SEL变量名=NSSelectorFromString(方法名字的字符串);
3NSString*变量名=NSStringFromSelector(SEL参数);
- 其中第1行是直接在程序里面写上方法的名字,第2行是写上方法名字的字符串,第3行是经过SEL变量得到方法的名字。咱们获得了SEL变量以后,能够经过下面的调用来给一个对象发送消息:
- [对象performSelector:SEL变量withObject:参数1withObject:参数2];
- 这样的机制大大的增长了咱们的程序的灵活性,咱们能够经过给一个方法传递SEL参数,让这个方法动态的执行某一个方法;咱们也能够经过配置文件指定须要执行的方法,程序读取配置文件以后把方法的字符串翻译成为SEL变量而后给相应的对象发送这个消息。
- 从效率的角度上来讲,执行的时候不是经过方法名字而是方法ID也就是一个整数来查找方法,因为整数的查找和匹配比字符串要快得多,因此这样能够在某种程度上提升执行的效率。
5.5,函数指针
- 在讲解函数指针以前,咱们先参看一下图5-2,函数指针的数值实际上就是图5-2里面的地址,有人把这个地址成为函数的入口地址。在图5-2里面咱们能够经过方法名字取得方法的ID,一样咱们也能够经过方法ID也就是SEL取得函数指针,从而在程序里面直接得到方法的执行地址。或者函数指针的方法有2种,第一种是传统的C语言方式,请参看“DoProxy.h”的下列代码片段:
- 1void(*setSkinColor_Func)(id,SEL,NSString*);
2IMPsay_Func;
- 其中第1行咱们定义了一个C语言里面的函数指针,关于C语言里面的函数指针的定义以及使用方法,请参考C语言的书籍和参考资料。在第一行当中,值得咱们注意的是这个函数指针的参数序列:
- 第一个参数是id类型的,就是消息的接受对象,在执行的时候这个id实际上就是self,由于咱们将要向某个对象发送消息。
- 第二个参数是SEL,也是方法的ID。有的时候在消息发送的时候,咱们须要使用用_cmd来获取方法本身的SEL,也就是说,方法的定义体里面,咱们能够经过访问_cmd获得这个方法本身的SEL。
- 第三个参数是NSString*类型的,咱们用它来传递skincolor。在Objective-C的函数指针里面,只有第一个id和第二个SEL是必需的,后面的参数有仍是没有,若是有那么有多少个要取决于方法的声明。
- 如今咱们来介绍一下Objective-C里面取得函数指针的新的定义方法,IMP。
- 上面的代码的第一行比较复杂,使人难以理解,Objective-C为咱们定义了一个新的数据类型就是在上面第二行代码里面出现的IMP。咱们把鼠标移动到IMP上,单击右键以后就能够看到IMP的定义,IMP的定义以下:
- typedefid(*IMP)(id,SEL,);
- 这个格式正好和咱们在第一行代码里面的函数指针的定义是同样的。
- 咱们取得了函数指针以后,也就意味着咱们取得了执行的时候的这段方法的代码的入口,这样咱们就能够像普通的C语言函数调用同样使用这个函数指针。固然咱们能够把函数指针做为参数传递到其余的方法,或者实例变量里面,从而得到极大的动态性。咱们得到了动态性,可是付出的代价就是编译器不知道咱们要执行哪个方法因此在编译的时候不会替咱们找出错误,咱们只有执行的时候才知道,咱们写的函数指针是不是正确的。因此,在使用函数指针的时候要很是准确地把握可以出现的全部可能,而且作出预防。尤为是当你在写一个供他人调用的接口API的时候,这一点很是重要。
5.6,Class类型
- 到目前为止,咱们已经知道了对应于方法的SEL数据类型,和SEL一样在Objective-C里面咱们不只仅能够使用对应于方法的SEL,对于类在Objective-C也为咱们准备了相似的机制,Class类型。当一个类被正确的编译事后,在这个编译成功的类里面,存在一个变量用于保存这个类的信息。咱们能够经过一个普通的字符串取得这个Class,也能够经过咱们生成的对象取得这个Class。Class被成功取得以后,咱们能够把这个Class看成一个已经定义好的类来使用它。这样的机制容许咱们在程序执行的过程中,能够Class来获得对象的类,也能够在程序执行的阶段动态的生成一个在编译阶段没法肯定的一个对象。
- 由于Class里面保存了一个类的全部信息,固然,咱们也能够取得一个类的超类。关于Class类型,具体的使用格式以下:
- 1Class变量名=[类或者对象class];
2Class变量名=[类或者对象superclass];
3Class变量名=NSClassFromString(方法名字的字符串);
4NSString*变量名=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)aCattlecolorParam:(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@implementationDoProxy
6-(void)setAllIVars
7{
8cattle[0]=[Cattlenew];
9
10bullClass=NSClassFromString(BULL_CLASS);
11cattle[1]=[bullClassnew];
12cattle[2]=[bullClassnew];
13
14say=@selector(saySomething);
15skin=NSSelectorFromString(SET_SKIN_COLOR);
16}
17-(void)SELFuncs
18{
19[selfdoWithCattleId:cattle[0]colorParam:@"brown"];
20[selfdoWithCattleId:cattle[1]colorParam:@"red"];
21[selfdoWithCattleId:cattle[2]colorParam:@"black"];
22[selfdoWithCattleId:selfcolorParam:@"haha"];
23}
24-(void)functionPointers
25{
26setSkinColor_Func=(void(*)(id,SEL,NSString*))[cattle[1]methodForSelector:skin];
27//IMPsetSkinColor_Func=[cattle[1]methodForSelector:skin];
28say_Func=[cattle[1]methodForSelector:say];
29setSkinColor_Func(cattle[1],skin,@"verbose");
30NSLog(@"Runningasafunctionpointerwillbemoreefficiency!");
31say_Func(cattle[1],say);
32}
33-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color
34{
35if(notFirstRun==NO)
36{
37NSString*myName=NSStringFromSelector(_cmd);
38NSLog(@"Runninginthemethodof%@",myName);
39notFirstRun=YES;
40}
41
42NSString*cattleParamClassName=[aCattleclassName];
43if([cattleParamClassNameisEqualToString:BULL_CLASS]||
44[cattleParamClassNameisEqualToString:CATTLE_CLASS])
45{
46[aCattlesetLegsCount:4];
47if([aCattlerespondsToSelector:skin])
48{
49[aCattleperformSelector:skinwithObject:color];
50}
51else
52{
53NSLog(@"Hi,Iama%@,havenotsetSkinColor!",cattleParamClassName);
54}
55[aCattleperformSelector:say];
56}
57else
58{
59NSString*yourClassName=[aCattleclassName];
60NSLog(@"Hi,youarea%@,butIlikecattleorbull!",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。
本章的内容很重要,但愿同窗们花一点时间仔细的理解一下。应该说,本章的内容有些使人难以理解,或者说知道了SEL,Class,IMP以后也许不知道如何使用,遇到什么状况的时候须要使用。不过在学习Objective-C的初级阶段,不知道这些也没有关系,可是SEL,Class,IMP的概念须要掌握,不然当你遇到别人写的质量比较高的代码或者苹果官方的技术文档的时候,你会以为理解起来比较吃力。
6,NSObject的奥秘
本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
在上一章里面,笔者向你们介绍了在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”上面,单击鼠标右键,在弹出菜单里面选择“JumptoDefinition”。而后会弹出一个小菜单,咱们选择“interfaceNSObject”。咱们能够看到以下代码
@interfaceNSObject<NSObject>{
Classisa;
咱们知道了,所谓的NSObject里面只有一个变量,就是Class类型的isa。isa的英文的意思就是isapointer的意思。也就是说NSObject里面只有一个实例变量isa。好的,咱们须要知道Class是一个什么东西,咱们把鼠标移动到“Class”上面,单击鼠标右键,在弹出菜单里面选择“JumptoDefinition”,咱们看到了以下的代码:
typedefstructobjc_class*Class;
typedefstructobjc_object{
Classisa;
}*id;
...
咱们在这里知道了,Class其实是一个objc_class的指针类型,咱们把鼠标移动到“objc_class”上面,单击鼠标右键,在弹出菜单里面选择“JumptoDefinition”,发现咱们仍是在这个窗口里面,Xcode并无把咱们带到objc_class的定义去,因此咱们无从知道objc_class内部到底是一个什么样的东西。
笔者顺便提一下,你们也许注意到了id的定义,id其实是objc_object结构的一个指针,里面只有一个元素那就是Class。那么根据上面咱们看到的,所谓的id就是objc_class的指针的指针。让咱们回忆一下下面的代码:
idcattle=[Cattlenew];
这句话是在初始化和实例话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”导入本章的项目里面。若是你没有第4章的代码,请到这里下载。若是你没有阅读第4章的内容,请参看这里。
- 第三步,把鼠标移动到项目浏览器上面的“Source”上面,而后在弹出的菜单上面选择“Add”,而后在子菜单里面选择“NewFile”,而后在新建文件对话框的左侧最下面选择“Other”,而后在右侧窗口选择“EmptyFile”,选择“Next”,在“NewFile”对话框里面的“FileName”栏内输入“MyNSObject.h”。而后输入(或者是拷贝也能够,由于这是C的代码,若是你很熟悉C语言的话,能够拷贝一下节省时间)以下代码:
- #include<stddef.h>
typedefconststructobjc_selector
{
void*sel_id;
constchar*sel_types;
}*MySEL;
typedefstructmy_objc_object{
structmy_objc_class*class_pointer;
}*myId;
typedefmyId(*MyIMP)(myId,MySEL,);
typedefchar*STR;/*Stringalias*/
typedefstructmy_objc_class*MetaClass;
typedefstructmy_objc_class*MyClass;
structmy_objc_class{
MetaClassclass_pointer;
structmy_objc_class*super_class;
constchar*name;
longversion;
unsignedlonginfo;
longinstance_size;
structobjc_ivar_list*ivars;
structobjc_method_list*methods;
structsarray*dtable;
structmy_objc_class*subclass_list;
structmy_objc_class*sibling_class;
structobjc_protocol_list*protocols;
void*gc_object_type;
};
typedefstructobjc_protocol{
structmy_objc_class*class_pointer;
char*protocol_name;
structobjc_protocol_list*protocol_list;
structobjc_method_description_list*instance_methods,*class_methods;
}Protocol;
typedefvoid*retval_t;
typedefvoid(*apply_t)(void);
typedefunionarglist{
char*arg_ptr;
chararg_regs[sizeof(char*)];
}*arglist_t;
typedefstructobjc_ivar*Ivar_t;
typedefstructobjc_ivar_list{
intivar_count;
structobjc_ivar{
constchar*ivar_name;
constchar*ivar_type;
intivar_offset;
}ivar_list[1];
}IvarList,*IvarList_t;
typedefstructobjc_method{
MySELmethod_name;
constchar*method_types;
MyIMPmethod_imp;
}Method,*Method_t;
typedefstructobjc_method_list{
structobjc_method_list*method_next;
intmethod_count;
Methodmethod_list[1];
}MethodList,*MethodList_t;
structobjc_protocol_list{
structobjc_protocol_list*next;
size_tcount;
Protocol*list[1];
};
- 第四步,打开06-NSObject.m文件,输入以下代码而且保存
- #import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
#import"MyNSObject.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
idredBull=[Bullnew];
SELsetLegsCount_SEL=@selector(setLegsCount:);
IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
IMPredBull_setLegsCount_IMP=[redBullmethodForSelector:setLegsCount_SEL];
[cattlesetLegsCount:4];
[redBullsetLegsCount:4];
[redBullsetSkinColor:@"red"];
Classcattle_class=cattle->isa;
MyClassmy_cattle_class=cattle->isa;
SELsay=@selector(saySomething);
IMPcattle_sayFunc=[cattlemethodForSelector:say];
cattle_sayFunc(cattle,say);
ClassredBull_class=redBull->isa;
MyClassmy_redBull_class=redBull->isa;
IMPredBull_sayFunc=[redBullmethodForSelector:say];
redBull_sayFunc(redBull,say);
[pooldrain];
return0;
}
- 第五步,在06-NSObject.m文件的窗口的“[pooldrain];”代码的左侧单击一下窗口的边框,确认一下是否出现一个蓝色的小棒棒,若是有的话那么断点被选择好了。如图6-2所示
- 图6-2,选择执行断点
- 第六步,选择Xcode上面的菜单的“Run”,而后选择“Debuger”,在Debuger窗口里面选择“BuildandGo”。
- 好的,你们就停在这里,不要作其余的操做,咱们把程序中断在程序几乎执行到最后的断点上,咱们将要经过Debuger来看看Objective-C内部究竟发生了什么样的奇妙的魔法。
- 注意在从编译到执行的过程中,会出现一些警告。因为本章程序指示用来阐述一些NSObject内部的东西,因此请忽略掉这些警告。固然,咱们在写本身的程序的时候,编译产生的警告通常是不能被忽略的。
6.3,超类方法的调用
- 咱们如今打开“06-NSObject.m”文件,发现下面的代码:
- SELsetLegsCount_SEL=@selector(setLegsCount:);
IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
IMPredBull_setLegsCount_IMP=[redBullmethodForSelector: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的数值不同。
- cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的地址彻底同样,说明他们使用的是相同的代码段。这种结果是怎样产生的呢?你们请打开“MyNSObject.h”,参照下列代码:
- structmy_objc_class{
MetaClassclass_pointer;
structmy_objc_class*super_class;
constchar*name;
longversion;
unsignedlonginfo;
longinstance_size;
structobjc_ivar_list*ivars;
structobjc_method_list*methods;
structsarray*dtable;
structmy_objc_class*subclass_list;
structmy_objc_class*sibling_class;
structobjc_protocol_list*protocols;
void*gc_object_type;
};
- 笔者在这里把开源代码的名字的定义加上了“my_”前缀,仅仅是为了区分一下。“MyNSObject.h”里面的代码问题不少,笔者历来没有也不会在实际的代码里面使用这段代码,使用这些代码的主要目的是为了向你们讲解概念,请你们忽略掉代码里面的种种问题。
- 咱们注意到这里的methods变量,里面包存的就是类的方法名字(SEL)定义,方法的指针地址(IMP)。当咱们执行
- IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
- 的时候,runtime会经过dtable这个数组,快速的查找到咱们须要的函数指针,查找函数的定义以下:
- __inline__IMP
objc_msg_lookup(idreceiver,SELop)
{
if(receiver)
returnsarray_get(receiver->class_pointer->dtable,(sidx)op);
else
returnnil_method;
- 好的,如今咱们的cattle_setLegsCount_IMP没有问题了,那么redBull_setLegsCount_IMP怎么办?在Bull类里面咱们并无定义实例方法setLegsCount:,因此在Bull的Class里面,runtime难道找不到setLegsCount:么?答案是,是的runtime直接找不到,由于咱们在Bull类里面根本就没有定义setLegsCount:。
- 可是,从结果上来看很明显runtime聪明的找到了setLegsCount:的地址,runtime是怎样找到的?答案就在:
- structmy_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”文件,请你们参考一下下面的代码:
- 1Classcattle_class=cattle->isa;
2MyClassmy_cattle_class=cattle->isa;
3SELsay=@selector(saySomething);
4IMPcattle_sayFunc=[cattlemethodForSelector:say];
5cattle_sayFunc(cattle,say);
6
7ClassredBull_class=redBull->isa;
8MyClassmy_redBull_class=redBull->isa;
9
10IMPredBull_sayFunc=[redBullmethodForSelector:say];
11redBull_sayFunc(redBull,say);
- 本节的内容和6.3节的内容比较相似,关于代码部分笔者认为就不须要解释了,若是同窗们有所不熟悉的话,能够参考一下第5章的内容。
- 在咱们的Cattle类和Bull类里面,都有saySometing这个实例方法。咱们知道只要方法的定义相同,那么它们的SEL是彻底同样的。咱们根据一个SELsay,在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”,参照以下代码:
- 1typedefstructobjc_ivar*Ivar_t;
2typedefstructobjc_ivar_list{
3intivar_count;
4structobjc_ivar{
5constchar*ivar_name;
6constchar*ivar_type;
7intivar_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不是必需的,可是对之后的章节的内容的理解会有一个很是好的辅助做用,但愿同窗们花费一点点心思和时间阅读一下。
7,对象的初始化以及实例变量的做用域
- 本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
- 到目前为止,咱们都使用的是下列方式建立对象
- [类名new];
- 这种new的方式,其实是一种简化的方式。笔者在这里总结一下前面几章里面曾经提到过关于建立对象的2个步骤:
- 第一步是为对象分配内存也就是咱们所说的allocation,runtime会根据咱们建立的类的信息来决定为对象分配多少内存。类的信息都保存在Class里面,runtime读取Class的信息,知道了各个实例变量的类型,大小,以及他们的在内存里面的位置偏移,就会很容易的计算出须要的内存的大小。分配内存完成以后,实际上对象里面的isa也就被初始化了,isa指向这个类的Class。类里面的各个实例变量,包括他们的超类里面的实例变量的值都设定为零。
- 须要注意的是,分配内存的时候,不须要给方法分配内存的,在程序模块总体执行的时候方法部分就做为代码段的内容被放到了内存当中。对象的内容被放到了数据段当中,编译好的方法的汇编代码被放到了代码段当中。在ObjectiveC里面,分配内存使用下列格式:
- 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<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
@private
boolgender;//male=YESfemale=NO
@protected
inteyesCount;
@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"
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
-(id)init
{
[superinit];
return[selfinitWithLegsCount: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;
returnself;
}
@end
- 第五步,打开“Bull.m”,,修改为下面的代码而且保存:
- #import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
NSLog(@"Ihave%deyes,mymasteris%@.",eyesCount,masterName);
//Listbelowisillegal
//NSLog(@"Mygenderis%@",gender?@"male":@"female");
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
@end
- 第六步,打开“07-InitWithAndIvarScope.m”,修改为下面的代码而且保存:
- #import<Foundation/Foundation.h>
#import"Bull.h"
#import"Cattle.h"
#import"MyNSObject.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
Bull*redBull=[[Bullalloc]initWithLegsCount:4
gender:YES
eyesCount:2
masterName:@"thatcowboy"];
[redBullsetSkinColor:@"red"];
[redBullsaySomething];
//legal,butnotgood
redBull->masterName=@"thatcowgirl";
//legal,butbad
//redBull->eyesCount=3;
//Tryingtoaccessaprivateivar,VERYbadthing
//MyClassbullClass=redBull->isa;
bool*redBullGender=(bool*)(redBull)+8;
NSLog(@"Mygenderis%@",*redBullGender?@"male":@"female");
[pooldrain];
return0;
}
- 第七步,选择屏幕上方菜单里面的“Run”,而后选择“Console”,打开了Console对话框以后,选择对话框上部中央的“BuildandGo”,若是不出什么意外的话,那么应该出现入图7-1所示的结果。若是出现了什么意外致使错误的话,那么请仔细检查一下你的代码。若是通过仔细检查发现仍是不能执行的话,能够到这里下载笔者为同窗们准备的代码。若是笔者的代码仍是不能执行的话,请告知笔者。
7.3,实例变量的做用域(Scope)
- 对于Objective-C里面的类的实例变量而言,在编译器的范围里面,是有做用域的。和其余的语言同样,Objective-C也支持public,private还有protected做用域限定。
- 若是一个实例变量没有任何的做用域限定的话,那么缺省就是protected。
- 若是一个实例变量适用于public做用域限定,那么这个实例变量对于这个类的派生类,还有类外的访问都是容许的。
- 若是一个实例变量适用于private做用域限定,那么仅仅在这个类里面才能够访问这个变量。
- 若是一个实例变量适用于protected做用域限定,那么在这个类里面和这个类的派生类里面能够访问这个变量,在类外的访问是不推荐的。
- 咱们来看看“Cattle.h”的代码片段:
- 1intlegsCount;
2@private
3boolgender;//male=YESfemale=NO
4@protected
5inteyesCount;
6@public
7NSString*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{
3NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
4NSLog(@"Ihave%deyes,mymasteris%@.",eyesCount,masterName);
5//Listbelowisillegal
6//NSLog(@"Mygenderis%@",gender?@"male":@"female");
7}
- 咱们来看看第3还有第4行代码,咱们能够访问legsCount,eyesCount还有masterName。
- 在第6行代码当中,咱们试图访问gender这个Cattle的私有(private)属性,这行代码产生了编译错误,因此咱们不得不注释掉第6行代码。
- 好的,咱们再来看看类的外部private,protected还有public的表现。请同窗们打开“07-InitWithAndIvarScope.m”,参考一下下面的代码:
- 1//legal,butnotgood
2redBull->masterName=@"thatcowgirl";
3//legal,butbad
4//redBull->eyesCount=3;
5
6//Tryingtoaccessaprivateivar,VERYbadthing
7//MyClassbullClass=redBull->isa;
8bool*redBullGender=(bool*)(redBull)+8;
9NSLog(@"Mygenderis%@",*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来得到偏移量的,可是笔者在这里偷了一个懒,先使用第七行的代码MyClassbullClass=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[superinit];
4return[selfinitWithLegsCount:4
5gender:YES
6eyesCount:2
7masterName:@"somebody"];
8}
9-(id)initWithLegsCount:(int)theLegsCount
10gender:(bool)theGender
11eyesCount:(int)theEyesCount
12masterName:(NSString*)theMasterName
13{
14legsCount=theLegsCount;
15gender=theGender;
16eyesCount=theEyesCount;
17masterName=theMasterName;
18returnself;
19}
- 从第3行到第7行,笔者重写了一下init。在init里面,笔者给经过调用initWith,给类的各个实例变量加上了初始值。这样写是很必要的,由于未来的某个时候,也许有人(或者是本身)很冒失的使用init来初始化对象,而后就尝试使用这个对象,若是咱们没有重写init,那么也许会出现一些意想不到的事情。
- 从第9行到第19行,是咱们本身定义的initWith,代码比较简单,笔者就不在这里赘述了。须要注意的一点是,笔者没有在这里调用[superinit];。缘由是Cattle的超类就是NSObject,初始化的过程就是初始化实例变量的过程,runtime已经为咱们初始化好了NSObject的惟一实例变量isa,也就是Cattle的类的信息,因此咱们不须要调用[superinit];。在某些时候,超类的变量须要初始化的时候,请同窗们在子类的init或者initWith里面调用[superinit];。
- 请同窗们再次打开“07-InitWithAndIvarScope.m”,参考下面的代码片段:
1Bull*redBull=[[Bullalloc]initWithLegsCount:4
2gender:YES
3eyesCount:2
4masterName:@"thatcowboy"];
5[redBullsetSkinColor:@"red"];
6[redBullsaySomething];
从第1行到第4行就是调用的initWith来初始化咱们的redBull。
7.5,本章总结
很是感谢你们对笔者的支持!
咱们在本章里面介绍了2个比较轻松的话题,一个是实例变量的做用域,这个概念笔者我的认为对有一点面向对象编程经验的人来讲,不是什么新鲜的概念了。可是须要注意的是,Objective-C并无强制咱们遵照它的规则,他仍旧为咱们提供了违反规则的机会,这一点上根C++比较相似。只要支持指针,就没法避免使用者违反规则。事务都是一分为二的,当咱们获得了访问任何变量的自由以后,咱们必须为访问这些变量承担后果。
第二个话题就是initWith。和其余的面向对象的语言不一样,Objective-C没有构造函数,它经过init还有initWith来初始化变量,咱们应该根据具体状况进行具体的分析,从而编写咱们的init还有initWith方法。
8,类方法以及私有方法
本系列讲座有着很强的先后相关性,若是你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
Objective-C里面区别于实例方法,和Java或者C++同样,也支持类方法。类方法(ClassMethod)有时被称为工厂方法(FactoryMethod)或者方便方法(Conveniencemethod)。工厂方法的称谓明显和通常意义上的工厂方法不一样,从本质上来讲,类方法能够独立于对象而执行,因此在其余的语言里面类方法有的时候被称为静态方法。就像@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语法所硬性规定的,因此也能够把这种手法成为一种潜规则。
- 关于潜规则常常看英文文档的同窗,应该能够遇到这样一个词,defactostandard,也就是笔者所说的潜规则。
本章所述的方法的私有化是一种有缺陷的手段,有必定的风险并且也没有彻底实现私有化,在后面的章节里面笔者会陆续的给出其余的实现方法私有化的方法。
另外,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<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
}
-(void)saySomething;
+(id)cattleWithLegsCountVersionA:(int)count;
+(id)cattleWithLegsCountVersionB:(int)count;
+(id)cattleWithLegsCountVersionC:(int)count;
+(id)cattleWithLegsCountVersionD:(int)count;
@end
- #import"Cattle.h"
#import<objc/objc-class.h>
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
+(id)cattleWithLegsCountVersionA:(int)count
{
idret=[[Cattlealloc]init];
//NEVERDOLIKEBELOW
//legsCount=count;
[retsetLegsCount:count];
return[retautorelease];
}
+(id)cattleWithLegsCountVersionB:(int)count
{
idret=[[[Cattlealloc]init]autorelease];
[retsetLegsCount:count];
returnret;
}
+(id)cattleWithLegsCountVersionC:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
return[retautorelease];
}
+(id)cattleWithLegsCountVersionD:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
if([selfclass]==[Cattleclass])
return[retautorelease];
SELsayName=@selector(saySomething);
MethodunknownSubClassSaySomething=class_getInstanceMethod([selfclass],sayName);
//ChangethesubclassmethodisRUDE!
MethodcattleSaySomething=class_getInstanceMethod([Cattleclass],sayName);
//method_impisdeprecatedsince10.5
unknownSubClassSaySomething->method_imp=cattleSaySomething->method_imp;
return[retautorelease];
}
@end
- 第四步,打开“Bull.h”和“Bull.m”,分别修改为为下面的代码而且保存:
- #import<Foundation/Foundation.h>
#import"Cattle.h"
@interfaceBull:Cattle{
NSString*skinColor;
}
-(void)saySomething;
-(NSString*)getSkinColor;
-(void)setSkinColor:(NSString*)color;
+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor;
@end
- #import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor
{
idret=[selfcattleWithLegsCountVersionC:count];
[retsetSkinColor:theColor];
//DONOTUSEautoreleasehere!
returnret;
}
@end
- 第五步,建立一个新类,名字叫作“UnknownBull”,而后分别打开“UnknownBull.h”和“UnknownBull.m”,分别修改为为下面的代码而且保存:
- #import<Foundation/Foundation.h>
#import"Bull.h"
@interfaceUnknownBull:Bull{
}
-(void)saySomething;
@end
- #import"UnknownBull.h"
@implementationUnknownBull
-(void)saySomething
{
NSLog(@"Hello,Iamanunknownbull.");
}
@end
- 第六步,打开“08-Class_Method_And_Private_Method.m”,修改为为下面的样子而且保存
- #import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
#import"UnknownBull.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle[5];
cattle[0]=[CattlecattleWithLegsCountVersionA:4];
cattle[1]=[BullcattleWithLegsCountVersionB:4];
cattle[2]=[BullcattleWithLegsCountVersionC:4];
cattle[3]=[BullbullWithLegsCount:4bullSkinColor:@"red"];
cattle[4]=[UnknownBullcattleWithLegsCountVersionD:4];
for(inti=0;i<5;i++)
{
[cattle[i]saySomething];
}
[pooldrain];
return0;
}
- 第七步,选择屏幕上方菜单里面的“Run”,而后选择“Console”,打开了Console对话框以后,选择对话框上部中央的“BuildandGo”,若是不出什么意外的话,那么应该出现入图8-1所示的结果。若是出现了什么意外致使错误的话,那么请仔细检查一下你的代码。若是通过仔细检查发现仍是不能执行的话,能够到这里下载笔者为同窗们准备的代码。若是笔者的代码仍是不能执行的话,请告知笔者。
8.2,方法的私有化
- 在讲述方法私有化以前,咱们首先要提到一个Objective-C里面的一个概念,动态类型和静态类型。
- 所谓的动态类型,就是使用id来定义一个对象,好比说
- idcattle=[[Cattlealloc]init];
- 所谓的静态类型,就是使用已知变量的的类型来定义对象,好比说
- Cattlecattle=[[Cattlealloc]init];
- 动态类型和静态类型各有好处,动态类型实现了多态性,使用静态类型的时候编译器会为你检查一下也许会出现危险的地方,好比说向一个静态类型的对象发送一个它没有定义的消息等等。
- 好的,咱们如今打开“cattle.h”,你们能够发现,和之前的版本相比,咱们的“cattle.h”少了一个方法的定义,那就是-(void)setLegsCount:(int)count;。笔者在本章的范例程序里面实现私有方法的手段比较简单,直接把-(void)setLegsCount:(int)count从“cattle.h”给删除掉了。
- 你们打开““cattle.m”,能够看到里面-(void)setLegsCount:(int)count是有实现部分的。实现部分和过去的版本没有任何区别的。
- 咱们本章里面讲述的实现方法私有化的手段,就是从头文件当中不写方法的声明。这样作会致使以下几个现象
- 1,在类的实现文件.m里面,你能够向日常同样使用[selfsetLegsCount: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{
3idret=[[Cattlealloc]init];
4//NEVERDOLIKEBELOW
5//legsCount=count;
6[retsetLegsCount:count];
7return[retautorelease];
8}
9+(id)cattleWithLegsCountVersionB:(int)count
10{
11idret=[[[Cattlealloc]init]autorelease];
12[retsetLegsCount:count];
13returnret;
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”,参看下面的代码
- 1cattle[0]=[CattlecattleWithLegsCountVersionA:4];
2cattle[1]=[BullcattleWithLegsCountVersionB:4];
- 咱们在回头看看本章程序的执行结果,心细的同窗也许发现了一个很严重的问题,咱们在第2行代码里面想要返回一个Bull的对象,可是输出的时候却变成了Cattle,缘由就是咱们在cattleWithLegsCountVersionB里面建立对象的时候,使用了idret=[[[Cattlealloc]init]autorelease]。因为Bull里面没有重写cattleWithLegsCountVersionB,因此除非咱们重写cattleWithLegsCountVersionB不然咱们向Bull发送cattleWithLegsCountVersionB这个类方法的时候,只能获得一个Cattle的对象。咱们能够要求咱们的子类的设计者在他们的子类当中重写cattleWithLegsCountVersionB,可是这样明显很是笨拙,失去了动态的特性。咱们固然有办法解决这个问题,如今请你们回到“Cattle.m”,参照下列代码:
- 1+(id)cattleWithLegsCountVersionC:(int)count
2{
3idret=[[selfalloc]init];
4[retsetLegsCount:count];
5return[retautorelease];
6}
- 咱们的解决方案就在第3行,咱们不是用静态的Cattle,而是使用self。说到这里也许你们有些糊涂了,在其余的语言当中和self比较相似的是this指针,可是在Objective-C里面self和this有些不大同样,在类函数里面的self实际上就是这个类自己。你们能够打开debugger观察一下,self的地址就是Bull的Class的地址。因此程序执行到上面的代码的第3行的时候,实际上就等同于idret=[[[Bullclass]alloc]init];
- 咱们能够在类方法里面使用self,咱们能否经过使用self->legsCount来访问实例变量呢?答案是不能够,由于在这个时候对象没有被建立也就是说,没有为legsCount分配内存,因此没法访问legsCount。
- 因为Bull类在程序被调入内存的时候就已经初始化好了,Bull类里面的实例函数应该被放到了代码段,因此从理论上来讲,咱们能够经过使用[selfsetLegsCount:count]来调用实例方法的,可是不幸的是Objective-C没有容许咱们这样作,咱们在类方法中使用self来做为消息的接收者的时候,消息老是被翻译成为类方法,若是发送实例方法的消息的话,会在执行的时候找不到从而产生异常。这样作是有必定的道理的,由于通常而言,实例方法里面不免要使用实例变量,在类方法当中容许使用实例方法,实际上也就容许使用实例变量。
- 关于self你们须要记住下面的规则:
- 1,实例方法里面的self,是对象的首地址。
- 2,类方法里面的self,是Class.
- 尽管在同一个类里面的使用self,可是self却有着不一样的解读。在类方法里面的self,能够翻译成classself;在实例方法里面的self,应该被翻译成为objectself。在类方法里面的self和实例方法里面的self有着本质上的不一样,尽管他们的名字都叫self。
- 请同窗们再次回到图8-1,能够发现经过使用神奇的self,咱们动态的建立了Bull类的对象。可是等一下,咱们的程序并不完美,由于Bull类的skinColor并无获得初始化,因此致使了null的出现。咱们在设计Cattle类也就是Bull的超类的时候,明显咱们没法预测到Bull类的特征。消除这种问题,咱们能够在获得了Bull对象以后使用setSkinColor:来设定颜色,固然咱们也能够直接写一个Bull类的方法,来封装这个操做,请同窗们打开“Bull.h”:
- +(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor;
- 咱们追加了一个类方法,bullWithLegsCount:bullSkinColor:用于建立Bull对象,请同窗们打开“Bull.m”:
- 1+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor
2{
3idret=[selfcattleWithLegsCountVersionC:count];
4[retsetSkinColor:theColor];
5//DONOTUSEautoreleasehere!
6returnret;
7}
- 上面这一段代码相信你们均可以看明白,笔者就不在这里赘述了。可是笔者须要强调一点,在这里咱们不须要调用autorelease的,由于咱们没有在这里建立任何对象。
- 通过了这个改造,经过在“08-Class_Method_And_Private_Method.m”里面咱们使用
- cattle[3]=[BullbullWithLegsCount:4bullSkinColor:@"red"];
- 使得咱们的代码终于正常了,请参照图8-1的第4行输出。
8.4,使用动态方法替换实现final功能
- 首先请同窗们打开“Cattle.m”,参照下面的代码片段:
- +(id)cattleWithLegsCountVersionD:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
if([selfclass]==[Cattleclass])
return[retautorelease];
SELsayName=@selector(saySomething);
MethodunknownSubClassSaySomething=class_getInstanceMethod([selfclass],sayName);
//ChangethesubclassmethodisRUDE!
MethodcattleSaySomething=class_getInstanceMethod([Cattleclass],sayName);
//method_impisdeprecatedsince10.5
unknownSubClassSaySomething->method_imp=cattleSaySomething->method_imp;
return[retautorelease];
}
@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关键字上面,点击鼠标右键盘,选择“Jumptodefinition”,咱们能够看到在文件“objc-class.h”里面的Method的定义。Method其实是类方法在Class里面的数据结构,系统会使用Method的信息来构筑Class的信息。在Method类型的声明里面,咱们看到了下面的代码
- typedefstructobjc_method*Method;
structobjc_method{
SELmethod_name;
char*method_types;
IMPmethod_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的实现方式,咱们固然能够使用一个文明的方法来告知子类的使用者,咱们不想让某个方法被重写。咱们只须要定义一个宏
- #defineFINAL
- 类的使用者看到这个FINAL以后,笔者相信在绝大多数时候,他会很配合你不会重写带FINAL定义的方法的