@interface 部分

使用 @interface 提供类的接口信息。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声明新类(继承自 NSObject 类)
@interface Circle : NSObject
{
// 告诉编译器 Circle 对象需要的各种数据成员
ShapeColor fillColor;
ShapeRect bounds;
}
// 方法声明
- (void) setFillColor: (ShapeColor) fillColor;
- (void) setBounds: (ShapeRect) bounds;
- (void) draw;
// 告诉编译器我们已经完成了类的声明
@end // Circle

熟悉中缀符:

Objective-C 有一种名为中缀符(infix notation)的语法技术,方法的名称及其参数都是合在一起的。例如,你可以这样调用一个带参数的方法:

[circle setFillColor: kRedColor];

带两个参数的方法调用如下所示:

[textThing setStringValue: @"hello there"
        color: kBlueColor];

setStringValuecolor: 实际上是参数的名称(实际上是方法名称的一部分,后面再详细介绍),@"hello there"kBlueColor 是被传递的参数。

注意冒号:

注意:冒号是方法名称非常重要的组成部分。方法

- (void) scratchTheCat;

不同于

- (void) scratchTheCat: (CatType) critter;

规则:如果方法使用参数,则需要冒号;否则不需要冒号。

@implementation 部分

@implementation 是一个编译器指令,表明你将为某个类提供代码。类名出现在 @implementation 之后。该行的结尾没有分号,因为在 Objective-C 编译器指令后不必使用分号。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@implementation Circle
- (void) setFillColor: (ShapeColor) c
{
fillColor = c;
} // setFillColor
- (void) setBounds: (ShapeRect) b
{
bounds = b;
} // setBounds
- (void) draw
{
NSLog (@"drawing a circle at (%d %d %d %d) in %@",
bounds.x, bounds.y,
bounds.width, bounds.height
colorName(fillColor));
} // draw
@end // Circle

实例化对象

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, const char * argv[])
{
id shapes[3];
shapeRect rect0 = { 0, 0, 10, 30 };
shapes[0] = [Circle new];
[shapes[0] setBounds: rect0];
[shapes[0] setFillColor: kRedColor];
[ShapeRect rect1 = { 30, 40, 50, 60 };
shapes[1] = [Rectangle new];
[shapes[1] setBounds: rect1];
[shapes[1] setFillColor: kGreenColor];
shapeRect rect2 = { 15, 19, 37, 29 };
shapes[2] = [OblateSphereold new];
[shapes[2] setBounds: rect2];
[shapes[2] setFillColor: kBlueColor];
drawShapes (shapes, 3);
return (0);
} // main

继承

语法

1
2
3
@interface Son : Father
@end // Son

注意:Objective-C 不支持多继承。下面的语句是不能被正常识别的:

@interface Circle: NSObject, PrintableObject

示例

父类

  • 声明:

1
2
3
4
5
6
7
8
9
10
@interface Shape : NSObject
{
ShapeColor fillColor;
ShapeRect bounds;
}
- (void) setFillColor: (ShapeColor) fillColor;
- (void) setBounds: (ShapeRect) bounds;
- (void) draw;
@end // Shape

  • 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation Shape
- (void) setFillColor: (ShapeColor) c
{
fillColor = c;
} // setFillColor
- (void) setBounds: (ShapeRect) b
{
bounds = b;
} // setBounds
- (void) draw
{
} // draw
@end // Shape

虽然 draw 方法没有实现任何功能,我们仍然需要定义它,以便 Shape 的所有子类都能被实现各自不同的方法。对于方法的定义,使用空正文或者返回一个虚(dummy)值都是可以的。

子类

子类的声明和实现现在变得非常简单:

  • 声明:

1
2
3
4
5
6
7
@interface Circle : Shape
@end // Circle
@interface Rectangle : Shape
@end // Rectangle

  • 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation Circle
- (void) draw
{
NSLog (@"drawing a circle at (%d %d %d %d) in %@",
bounds.x, bounds.y,
bounds.width, bounds.height,
colorName(fillColor));
} // draw
@end // Circle
@implementation Rectangle
- (void) draw
{
NSLog (@"drawing a rect at (%d %d %d %d) in %@",
bounds.x, bounds.y,
bounds.width, bounds.height,
colorName(fillColor));
} // draw
@end // Circle

重载

Objective-C 提供某种方式来重载方法,并且仍然调用超类的实现方式。当需要超类实现自身的功能,同时在前面或后面执行某些额外的工作时,这种机制非常有用。为了调用继承方法的实现,需要使用 super 作为方法调用的目标。

示例

例如,如果要改写绘图软件,将所有原本是红色的圆统一绘制成绿色。因为 setFillColor: 是在 Shape 类中定义的。因此,只要在 Circle 类中重载 setFillColor: 方法就可以解决该问题。我们考虑颜色参数,如果它是红色,就把它改成绿色。然后,使用 super 通知超类(shape)将更改的颜色存储到 fillColor 实例变量中。

1
2
3
4
5
6
7
8
9
10
@impletement Circle
- (void) setFillColor : (ShapeColor) c
{
if (c == kRedColor) {
c = kGreenColor;
}
[super setFillColor: c];
} // setFillColor
// Circle 类其他实现部分保持不变
@end // Cicle

Tips: 重载方法时,调用超类方法总是一个不错的选择,这样可以确保能够获得方法实现的所有特性。

复合

在 Objective-C 中,复合是通过包含作为实例变量的对象指针实现的。

例如,一辆轿车由一个发动机(Engine)和四个轮子(Tire)组成,下面我们用程序描述它。

示例

Tire 类

Tire 类只有一个 description 方法:

1
2
3
4
5
6
7
8
9
10
@interface Tire : NSObject
@end // Tire
@implementation Tire
- (NSString *) description
{
return (@"I am a tire. I last a while");
}
@end // Tire

Engine 类

与 Tire 类类似,Engine 类也只有一个 descrption 方法:

1
2
3
4
5
6
7
8
9
10
@interface Engine : NSObject
@end // Engine
@implementation Engine
- (NSString *) description
{
return (@"I am an Engine. Vroom!");
}
@end // Engine

Car 类

  • 声明:

Car 类本身拥有一个 engine 对象和一个由 4 个 tires 对象组成的 C 数组。它通过复合的方式来组装自己。 Car 同时还有一个 print 方法,该方法使用 NSLog 输出轮胎和发动机的描述:

1
2
3
4
5
6
7
8
9
@interface Car : NSObject
{
Engine *engine;
Tire *tires[4];
}
- (void) print;
@end // Car

每一个 Car 对象都会为指向 engine 和 tires 的指针分配内存。但是真正包含在 Car 中的并不是 engine 和 tires 变量,而只是内存中存在的其他对象的引用。为新建的 Car 分配内存时,这些指针将被初始化为 nil (零值),也就是说这辆汽车现在还没有发动机也没有轮胎。你可以将他想象成还在流水线上组装的汽车框架。

  • 实现:

下面让我们看看 Car 类的实现。首先是一个初始化实例变量的 init 方法。init 方法创建一个 engine 变量和 4 个 tires 变量,用以装配汽车。使用 new 创建新对象时,实际上系统要完成两个步骤:

  1. 为对象分配内存,即对象获得一个用来存放其实例变量的内存块;
  2. 调用 init 方法,让该对象处于可用状态。

Car
1
2
3
4
5
6
7
8
9
10
11
12
13
- (id) init
{
if (self = [super init]) {
engine = [Engine new];
tires[0] = [Tire new];
tires[1] = [Tire new];
tires[2] = [Tire new];
tires[3] = [Tire new];
}
return (self);
} // init

  • 关于 self :

记住,每个方法调用都获得了一个名为 self 的隐藏参数,它是一个指向接收消息的对象的指针。方法使用 self 参数查找它们要使用的实例变量。

  • 关于 if 语句:

在 init 方法中,下面这行代码看起来有点奇怪:

if (self = [super init]) {

下面我们来解释这行的意思。若要超类(这里是 NSObject)可以完成所需的一次性初始化,需要调用 [super init] 。init 方法返回的值(id 型数据,即泛型对象指针)描述了被初始化的对象。

[super init] 的结果赋给 self 是 Objective-C 的标准惯例。这么做是为了防止超类在初始化过程中返回的对象不同于原先创建的对象。

Car 类的 init 方法创建了 4 个新轮胎并将其赋值给 tires 数组,接着又创建了一台发动机并将其赋值给 engine 实例变量。

接下来就是 Car 的 print 方法:

1
2
3
4
5
6
7
8
9
10
11
- (void) print
{
NSLog (@"%@", engine);
NSLog (@"%@", tires[0]);
NSLog (@"%@", tires[1]);
NSLog (@"%@", tires[2]);
NSLog (@"%@", tires[3]);
} // print
@end // Car

main 函数

最后给出 main() 函数的实现:

1
2
3
4
5
6
7
8
9
int main (int argc, const char * argv[])
{
Car *car;
Car = [Car new];
[car print];
return (0);
} // main

存取方法

存取方法(accesosor method)是用来读取或修改对象特定属性的方法。存取方法包含两种,一种是 getter方法 ,一种是 setter方法

命名习惯

在 Objective-C 中,setter 方法的命名习惯与其他语言类似:根据它所更改的属性的名称来命名,并加上前缀 set 。几个示例:setEngine:setStringValue:setFont:setTextLineHeight:

而 getter 方法的命名习惯和其他语言则有较大区别:仅仅根据其返回的属性的名称来命名。几个示例:enginestringValuefontfillColortextLineHeight

注意:不要将 get 用作 getter 方法的前缀。例如,方法 getStringValuegetFont 就破坏了命名惯例。get 这个词在 Cocoa 中有着特殊的含义。如果 get 出现在 Cocoa 的方法名称中,就意味着这个方法会通过你当做参数传入的指针来返回数值。如果你在存取方法的名称中使用 get ,那么有经验的 Cocoa 编程人员就会想到将指针作为参数传入这个方法。当他们发现这不过是一个简单的存取方法时就会感到困惑。最好不要让其他程序员被代码搅得一头雾水。

示例

声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface Car : NSObject
{
Engine *engine;
Tire *tires[4];
}
- (Engine *) engine;
- (void) setEngine: (Engine *) new Engine;
- (Tire *) tireAtIndex: (int) index;
- (void) setTire: (Tire *) tire
atIndex: (int) index;
- (void) print;
@end // Car

实现

  • 发动机的存取方法:

1
2
3
4
5
6
7
8
9
- (Engine *) engine
{
return (engine);
} // engine
- (void) setEngine : (Engine *) newEngine
{
engine = newEngine;
} // setEngine

  • 轮胎的存取方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void) setTire: (Tire *) tire
atIndex: (int) index
{
if (index < 0 || index > 3) {
NSLog(@"bad index (%d) in setTire:atIndex:", index);
exit (1);
}
tires[index] = tire;
} // setTire:atIndex
- (Tire *) tireAtIndex : (int) index
{
if (index < 0 || index > 3) {
NSLog (@"bad index (%d) in tireAtIndex:", index);
exit (1);
}
return (tires[index]);
} // tireAtIndex:

main 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main (int argc, const char * argv[])
{
Car *car = [Car new];
Engine *engine = [Engine new];
[car setEngine: engine];
int i;
for (i = 0; i < 4; i++) {
Tire *tire = [Tire new];
[car setTire: tire
atIndex: i];
}
[car print];
return (0);
} // main