分类:
2005-11-27 16:37:28
Objective-C入门
A first look at ObjC(中文版)
原著:Noah Roberts (jik)
翻译:jeff_yecn
顾名思义,Objective-C就是面向对象的C语言。你可以使用Objective-C编程,但不使用任何非C语言的东西。但那样将是对这语言的浪费。Objective-C使用所有的C语言的内容,同时添加了一些使得它更具面向对象特征的语法。本指南假定你已经知道什么是面向对象编程,同时熟悉C(你不必是这方面的专家,但是我不打算在这里教你应该已经知道的东西)。
我同时必须建议你不要仅仅读这一篇文章,因为它远远不是在Objective-C(ObjC)方面的终极资源。在互联网上你可以找到其它更好、更详细的学习这种语言的资料。在我的网站:members.xoom.com/jik_you上有一些链接,也许你可以从那里开始搜索。
ObjC,和其他面向对象语言一样,是围绕着对象的,所以让我展示一下对象的构成。在ObjC中,对象有两部分。它有一个界面,是对象的外在表现。同时它有一个实现,就是它的内部工作机制。换句话说,就是外界看到的部分和看不到的部分。有时,这样更容易理解。
界面
在ObjC中,界面通常有它自己的头文件(通常是.h),但并不是一定要这样。你可以把界面和实现放在一个文件中,或者把几个界面放在一起,但每个界面是不同的,你必须对此很清楚,否则将会感到非常苦恼。我将只使用在头文件中定义界面的办法。
首先,你必须导入这个对象的超类的界面。你可以使用与“#include”相同语法的预处理标识“import”。事实上,两者之间的区别只是#import会进行检查以确保文件仅被包含一次。有些人选择使用#include,这完全符合语法,但ObjC设计是使用#import,你也许愿意考虑这一点而保护你的界面文件。
如果你使用gcc(或egcs等)来作为你的编译器,也许你会愿意使用开关:-Wno-import,否则,gcc会无休止地提示你使用import来代替include。
因此,以下是以一个对象为超类的新类的第一行代码,也通常是所有类的开头。
#import /* gcc运行时库的对象 */
现在,如果你计划引用其他的类,你可以导入他们的头文件,更好的办法是使用@class来声明这些标识是类。界面并不需要知道这个类是什么和怎么做的全部信息,所以引用它的头文件并没有作用,让我们使用@class。
@class String; //简单吧?
顺便说一下,ObjC同时使用“//”和“/* */”来作为注释,这有时会比较方便。
好了,现在我们准备让世界知道想要什么类型的对象,所以让我们声明它。首先我要说明的是我在声明一个新类,在此之前,你应该定义好这个新类的超类。
@interface NewClass : Object //新类在左边,超类在右边。
我并没有漏掉“;”,就是这样写的。
现在我们声明实例变量,不管他们是不是私有的(我碰巧认为私有的东西是不应该被人看见的,实际上也是这样:P)。我在一对“{}”中间写上,就象我们在C的结构中做的那样。
{
@public
int aValue;
@protected //缺省情况下只能被子类所见
int value2;
@private //不可见
int mrInvisible;
}
做完这些以后,我们要放入method的原型定义。method只能是公共的,虽然如果我愿意的话,我可以演示给你看如果你访问一个私有的method的时候会产生的编译器警告信息。
method既有“类”的(也有些人叫做“工厂”的),也有“实例”的。类method只能由类对象访问,实例method显然由实例访问。例如,我声明了一个保存实例的变量,但在我用它之前,我必须使用一个类method来产生一个这个变量指向的实例,这样我才可以调用它的实例method。我这样做了以后,我就不能再访问它的类method了。在我释放对象实例之前,我只能访问它的类method。描述一个method时,你根据它是类method还是实例method响应在前面放上“+”或“-”的符号。然后,在“( )”中你填入method返回值的类型,除非你返回的是代表任何对象的通用类型id。在然后,如果你的method有参数的话,你先写一个“:”,跟着是在“( )”中间的参数类型,再接着是变量名。你可以一直写下去,直到以“;”结束。
同时,在第一个参数之后和下一个“:”之前,你还可以放入一些单词,但单词与“:”之间不能有空格。
也许你现在已经糊涂了,让我们看一个类和实例method。
+createWith: (int)value; //足够简单
-(void)say: (String *)what to: (String *)name; //第一个参数是what,
// to是method名称的一部分。
在参数表中间使用额外的单词可以使得method念起来更容易理解。就象上面第二个method,念起来象“say 'Hello' to jik”,而不是“say 'Hello', jik”。
可变长度参数表的语法和C语言中是一样的:
-(void)doStuff: (id)format,...;
最后,当我们完成全部的定义以后,我们通过下面的语句告诉编译器我们完成了:
@end
这里谈的都是关于对象的。你可以使用typedef或其他在C中可以放入头文件的语法继续定义对象。
名字的小规则
method名由除了+/-,类型和参数以外的所有东西组成。例如,method“-(void)say: (String *)what to: (String *)name”的名字在内部是“say:to:”。如果你使用gdb ObjC补丁,这也是用名字引用method的方法。
同时,你应该记住ObjC通常会被处理为C语言并传递到C编译器。过程中名字通常会发生改变,你应该牢记这一点而不要做可能会迷惑编译器的事.....就好象:
-roll_d: (int)sides;
-roll: (int)count d: (int)sides;
在这个例子中,两个method都会被gcc编译器转化为以下的C函数:
__i_ClassName_roll_d_()
这个事情发生过在我身上,所以相信我,千万小心。如果我是你的话,我不会在method名的中间使用“_”,在开头使用则没有什么问题。
消息
由于你在实现中将经常使用消息,我们必须首先讨论他们是什么以及在语法上怎么表达。一个消息只是对象method的简单调用。在面向对象编程中,由于它就好象给一个对象发送消息告诉它做某些事,因此称作消息。要表达一个消息,首先是对象的名字,跟着是你要它执行的method以及你要传递的参数,这一切用“[ ]”包围起来。
例如:
[object jumpAround];
或
[object jump: forward];
“object”可以是一个类也可以是类的一个实例。通常类以大写字母开头,而实例则以小写开头。如果“object”是类的话,你只能要求它执行类method,反之亦然。
例如,要建立类“Object”的一个新实例,我们可以使用...:
[Object alloc];
“alloc”是为新对象分配内存的类method。但这样并不能使得对象能够使用,因为它的实例变量还没有初始化...要那样做的话,我们调用“init”...因此,我们需要做的全部事情是:
id object=[Object alloc];
[object init];
如果消息的返回值的类型合适的话,你也可以使用它作为另一个消息的一部分。由于“alloc”消息返回id类型,我们可以用这个返回值作为消息发送的对象。由于空返回值不能做任何事情,因此通常返回id会比返回void要好。在你的method执行完成后,最好能够返回指向当前对象自身的变量“self”,除非你不想它作为其他消息的参数或发送对象...如果那样的话,返回void吧。一个使用空返回值的好例子是释放内存的method,不好的例子是分配内存的时候。所以,我们可以使用这个特性来在行中完成分配内存和初始化的工作:
id object=[[Object alloc] init];
当然,你必须确定init method也返回id,否则我们不能继续使用“object”变量。
消息的执行按照先进先出的顺序。如果你把一个消息作为另一个消息的一部分,所有的内层的消息回在外层的消息执行前执行和返回。最终的返回值为最后一个执行的消息的返回值,内层的消息不会在最后返回。
实现
这是对象完成所有工作的部分。它定义所有对象的method的内部工作机制,准确的说,是所有非继承的部分。实现文件的后缀为“.m”。
首先,你需要导入类自己的和这个对象中将要用到的类的头文件。象通常一样,你可以做额外的define,typedef以及其他你可能会在C源程序开头做的事情。
当你完成所有的准备工作,你告诉编译器你开始定义一个新对象的实现部分。你用与界面定义类似的method来进行,但你不在需要指明超类是什么,除非你没有为这个对象创建一个界面(这是可能做到的)。
当然,如果你愿意的话,你也可以在说明一次超类是什么,没有任何规定说明你不能那样做。你可以用创建界面定义一样的method来做。
@implementation NewClass
在这里你不需要添加实例变量,所以在实现中不需要包括那一部分。我们现在要做的是定义新的method,以及我们需要覆盖的继承下来的method。有关的语法是一种界面中的 method声明和C函数的交叉。
以下是一个简短的method定义的例子:
-sayHello
{
printf("Hello world! ");
return self;
}
下面一个例子是有参数使用消息的:
-say: (String *)What
{
printf("%s ",[what cString]);
return self;
}
你用与界面定义相同的方法结束实现定义。
@end
特殊的method
当你在派生一个类的时候,你要知道有些method的情况有些特殊。在不同的API中,它们的称呼有所不同,但其中相似的已经分类在一起。有些method在多数情况下都不应该被覆盖,有些可以被覆盖,在你这样这样做的时候必须满足一些特定的要求。
你不应该覆盖“alloc”method。这个method做了些特别的事情来为新的实例分配存储空间。它已经被在Object