类的实现
类的定义在结构上和类的声明很相似。类的定义以@implementation命令字开始,以@end命令字结束: @implementation 类名称:超类名称
{
实例变量声明
}
方法实现
@end
需要说明的一点是,类的实现文件中必须引入对应的接口文件。例如,在Rectangle.m文件中就必须引入Rectangle.h。另外,由于类的实现中没有必要再次重复其引入文件中的内容,因此这些类容可以被省略掉:
● 其超类的名称
● 类中实例变量的声明
这样一来,引入其对应的接口文件简化了实现文件,并使得在实现文件中我们更能聚焦于类中各个方法的实现:
#import "类名称.h"
@implementation 类名称
方法的实现
@end
其中方法的实现体和C语言中是一样的,用一对大括号括起来。在大括号之前内容是和接口文件中方法的声明一样,只是少了声明时最后面的分号。例如:
+(id)alloc
{
...
}
-(BOOL)isFilled
{
...
}
-(void)setFilled:(BOOL)flag
{
...
}
使用变参的方法对参数的处理和普通的方法一样:
#import <stdarg.h>
...
- getGroup:group,...
{
va_list_ap;
va_start(ap,group);
...
}
引用实例变量
缺省情况下,实例方法中是可以访问类中所有的实例变量的。使用时直接使用其名称就可以了。尽管编译器会创建一个对等的C结构体来存储这些实例变量,但是这个结构体对外是不可见的。我们不需要使用结构体运算符(.或者是->)来操作其中的数据。例如:下面方法的定义中使用到了接收者的filled变量: -(void)setFilled:(BOOL)flag
{
filled = flag;
}
尽管其中的filled变量以及消息的接收者都不是作为参数传入到方法中的,但是在类的方法实现中仍然是可以访问的。这种语法大大简化了Objective-C代码的编写。
当一个对象的实例变量不是消息的接收者的时候,实例变量的类型必须被通过静态类型的方式显示地告知编译器。在引用这些静态类型的实例变量的时候,我们要使用结构体的指针运算符。
例如,Sibling类中声明了静态类型的twin作为其中的一个实例变量:
@interface Sibling: NSObject
{
Sibling *twin;
int gender;
struct features *appearance;
}
只要静态类型对象的实例变量的作用域包含了这个类(正如上面的程序段中那样,由于twin实例变量的类型和类是一样的),Sibling类的方法就可以直接设置其值:
- makeIdenticalTwin
{
if (!twin)
{
twin = [[Sibling alloc] init];
twin->gender = gender;
twin->appearance =appearance;
}
}
实例变量的作用域
尽管实例变量是在接口文件中进行声明的,但是实例变量表达的则更多的是类的实现方式而不是类的使用方法。对象对外界提供的接口是方法而不是其自身内部的数据。通常每一个实例变量都有一个与之一一对应的方法。如下:
-(BOOL) isFilled
{
return filled;
}
但是这样做不是必须的。某些方法返回的可能不是存储在实例变量中的信息;并且有些实例变量所存储的信息是不希望能被外界能感知的。
在类的不断完善的过程中,尽管其方法可能保持不变,但是实例变量则有更大的可能性发生变化。但是只要类的实例间交互的消息没有发生变化,则这种内部实例变量的变化并不会影响到类对外界提供的接口。
为了强迫对象对外界隐藏其数据,编译器对实例变量进行了限制,也就是说限制了这些实例变量在程序中的可见范围。但是出于灵活性的考虑,允许程序员显示地对这种限制范围做出下面四种选择之一:
┌───────────┬───────────────────────────────────────────────────────────────────────────────┐
│ 命令字 │ 含义 │
├───────────┼───────────────────────────────────────────────────────────────────────────────┤
│ @private │实例变量只能在声明他的类中被访问 │
├───────────┼───────────────────────────────────────────────────────────────────────────────┤
│ @protected│实例变量只能在声明他的类及其该类的派生类中被访问 │
├───────────┼───────────────────────────────────────────────────────────────────────────────┤
│ @public │实例变量在任何地方都可以被访问 │
├───────────┼───────────────────────────────────────────────────────────────────────────────┤
│ │现代运行时系统中,@package类型的实例变量对于实现其类的可执行的“影像”来说是可见 │
│ │的;而对于其他代码来讲都是@private的。 │
│ │Objective-C中的@package变量的作用域类似于C中的private_extern变量的作用域。任何 │
│ @package │位于该类的实现“影像”之外的地方对其的访问都会导致链接错误。 │
│ │这种作用域的限制对于framework框架类来说是非常有用的。此时@private的限制太严格了 │
│ │而@protected或者是@public的限制又太宽松了。 │
└───────────┴───────────────────────────────────────────────────────────────────────────────┘
译者注:关于@package含义的描述中用到了“影像”一词,原文中的词为“image”.鉴于还没有找到没有合适的中文词汇来对其进行描述,这里说说译者对其的理解:这里的image通常指的是framework框架,动态库或者可执行文件,而与“图像”一词毫无关系。它指的是链接器在进行链接时能加载的“影像”。一个常见的运行时错误就是:“dyld image not found",这里的image就是这个意思。更多信息可以参考: http://stackoverflow.com/questions/772600/what-does-the-package-directive-do-in-objective-c,不过是英文的哟。┌────────────────────┐ ─┐ ─┐ ─┐
│ │ │ │ │
│ 声明实例变量的类 │ ├───>@private │ │
│ │ │ │ │
└─────────┬──────────┘ ─┘ │ │
│ ├───>@protected │
┌─────────┴───────────┐ │ │
│ │ │ │
│ 继承实例变量的类 │ │ ├──>@public
│ │ │ │
└─────────────────────┘ ─┘ │
│
│
┌─────────────────────┐ │
│ │ │
│ 其他的无关的代码 │ │
│ │ │
└─────────────────────┘ ─┘
@interface Worker : NSObject
{
char * name;
@private:
int age;
char * evaluation;
@protected:
id job;
float wage;
@public:
id boss;
}
缺省情况下,所以没有显示作用域命令字的变量都是@proetected。
类中声明的所有变量,不管其前面的命令字是什么,在该类范围内都是可见的。例如,定义了job实例变量的类,如上面的Worker类,是可以在其方法中使用job这个变量的:
- promote:newPosition
{
id old = jog;
job = newPosition;
return old;
}
很显然,如果类不能访问他的实例变量,那么这些实例变量将变得毫无用处。
一般情况下,类还可以访问其继承而来的实例变量。类能够在其数据结构的作用范围内访问之,这样做是有意义的,特别是当我们把一个类看作是对其基类的更精细化的描述的时候。之前的promoteTo:方法是可以定义在任何从Worker类中继承了job实例变量的派生类中的。
然而,对这种继承做出限制以避免对这些实例变量的直接访问也是有必要的:
● 一旦派生类中访问了继承而来的实例变量,那么声明这些实例变量的类就被绑定到了这部分实现上。在以后的版本中,如果没有经过仔细考虑对派生类的影响,就不能删除这些变量或者是修改这些实例变量所扮演的角色。
● 更有甚者,如果派生类中访问了或者是修改了继承而来的实例变量的值,就有可能为声明这些实例变量的类中引入bug,特别是当这些实例变量只是和声明他们的类内部密切相关的时候。
为了达到把实例变量的作用域仅限于声明他的类中 ,我们必须使用@private命令字。被@private命令字限定的实例变量只能被派生类通过其所在类提供的公有接口来访问。如果其所在的类没有提供对应的公有接口,则这些实例变量对与派生类来说是不可见的。
另外的一个极端就是@public命令字。他使得实例变量成为通用的变量,即使是在声明他的类的范围之外,或者是其派生类的范围之外。一般情况下,别的对象必须通过发送消息的方式来获取存储在一个实例变量中的信息。然而,公有的实例变量却是可以像C语言的结构体中的字段一样在程序的任何地方都可以被直接访问。
Worker *ceo = [[Worker alloc] init];
ceo->boss = nil;
此时对象必须是静态类型的。
@public命令字破坏了对象隐藏其数据的能力。这种做法与面向对象的编程思想--数据是被封装在对象中的,以避免由于粗心大意而引发的错误--背道而驰。因此共有的实例变量在非特殊情况下是应该极力避免的。