Objective-C – 定义一个类

原文:The Objective-C Programming Language: Defining a Class
翻译:Sam Sha – ycoder.com

Much of object-oriented programming consists of writing the code for new objects—defining new classes. Objective-C 中类包括两部分

  • 接口部分 - 声明方法和实例变量,类型以及他的基类
  • 实现部分- 类的实际定义部分 (包括方法的实现体)

他们通常写在两个文件中,有时候分成多个文件,通过使用一种叫做“ category”的功能。类目可以分割一个类定义或者扩展已经存在的类。详见“Categories and Extensions.”

源文件

虽然编译器不需要他们,接口和实现通常分放在两个不同的文件中,接口文件必须可以被任何使用这个类的获取

单个文件可以声明和实现多个类,然而,习惯上每个类有一个单独的接口文件,同样单独的实现文件。保持类接口的分离可以更好的反映独立实例的地位

接口和实现文件通常以类名命名,实现文件以 .m后缀,其中包含Objective-C的源代码,接口文件可以指定其他任何后缀。因为他包含在其他源码文件中,通常以 .h 后缀作为头文件。如 Recangle.h 和 Rectangle.m

Separating an object’s interface from its implementation fits well with the design of object-oriented programs. An object is a self-contained entity that can be viewed from the outside almost as a “black box.” Once you’ve determined how an object interacts with other elements in your program—that is, once you’ve declared its interface—you can freely alter its implementation without affecting any other part of the application.

类接口

类接口的定义以 @interface 编译器指令开头,@end 结尾(所有的Objective-C编译器指令都是以 “@” 打头)

@interface ClassName : ItsSuperclass
{
    instance variable declarations
}
method declarations
@end

The first line of the declaration presents the new class name and links it to its superclass. The superclass defines the position of the new class in the inheritance hierarchy, as discussed under “Inheritance.” If the colon and superclass name are omitted, the new class is declared as a root class, a rival to the NSObject class.

Following the first part of the class declaration, braces enclose declarations of instance variables, the data structures that are part of each instance of the class. Here’s a partial list of instance variables that might be declared in the Rectangle class:

float width;
float height;
BOOL filled;
NSColor *fillColor;

方法 – 类方法接下来声明,在大括号块之后,这些方法名称可以用于类对象,类方法以加号打头:

+ alloc;

类实例使用的方法,实例方法,以减号打头:

- (void)display;

虽然不常用,但你可以定义相同名称的类方法和实例方法。方法名称可以与实例变量相同,如:圆有一个 radius 方法匹配 radius 实例变量

方法返回类型使用标准C语法声明

- (float)radius;

参数类型相似的方式声明

- (void)setRadius:(float)aRadius;

如果没有声明返回参数类型,默认返回 id ,上面的 alloc 方法返回 id

如果有多个参数,这些参数在方法名冒号之后声明,参数声明如在消息中方式

- (void)setWidth:(float)width height:(float)height;

方法可变参数,使用逗号和省略号声明

- makeGroup:group, ...;

引入接口

接口文件必须包含在任何依赖于这个类接口的源模块中——包括创建这类实例,发送消息调用这个类上声明的方法,或者提及该类声明的实例变量的任何模块。接口通常使用 #import 指令引入

#import "Rectangle.h"

这个指令与 #incloude 相同,除了能保证相同的文件只导入一次外。因此他是首选

To reflect the fact that a class definition builds on the definitions of inherited classes, an interface file begins by importing the interface for its superclass:

#import "ItsSuperclass.h"
@interface ClassName : ItsSuperclass
{
    instance variable declarations
}
method declarations
@end

这条规则意味着每个接口文件都包含或者间接包含所有继承类的接口文件。当源代码模块中导入了一个类接口,他就得到这个类所建立在的整个继承关系所有接口。

注意如果有预编译——一个预先编译的头——支持这个基类,你可以通过引入这个预编译替代

引用其他类

一个接口文件中声明了一个类,通过导入父类,隐式包含了所有继承的类,从 NSObject 下到他的基类。如果这个接口引用的类不在这个结构中,他必须显式引入或者直接通过 @class 声明。

@class Rectangle, Circle;

这里直接简单的告诉编译器“Recangle”和“Circle”是类名,他没有引入他们的接口文件

接口文件提及的类名包括直接输入的实例变量,返回值,参数。如下面的定义

- (void)setPrimaryColor:(NSColor *)aColor;

提及了 NSColor 类

如果声明只是简单的使用类名作为类型,而不依赖这个类具体的实现(他的方法和实例变量),这个 @class 指令给编译器足够期望的预警。但是,如果一个类的接口被实际用到(实例创建,消息发送),这个类的接口必须被引入,通常,接口文件中使用 @class 声明类,对应的实现文件中导入他们的接口(当需要创建这些类的实例或者发送他们消息时)

@class 指令减少了编译器和连接器查看代码的次数,因此是给出类声明最简单的方式。简单,避免了导入文件同时需要其他文件可能带来的问题,如果一个类声明了一个另一静态类型的实例变量,而他们两个的接口文件相互引入了对方,则两者都无法编译成功。

接口职责

接口文件的目的是声明新类到其他源模块(和其他程序员),他包含了使用这个类的所有信息(程序员可能还可以看到一些文档)

  • The interface file tells users how the class is connected into the inheritance hierarchy and what other classes—inherited or simply referred to somewhere in the class—are needed.
  • The interface file also lets the compiler know what instance variables an object contains, and tells programmers what variables subclasses  inherit. Although instance variables are most naturally viewed as a matter of the implementation of a class rather than its interface, they must nevertheless be declared in the interface file. This is because the compiler must be aware of the structure of an object where it’s used, not just where it’s defined. As a programmer, however, you can generally ignore the instance variables of the classes you use, except when defining a subclass.
  • Finally, through its list of method declarations, the interface file lets other modules know what messages can be sent to the class object and instances of the class. Every method that can be used outside the class definition is declared in the interface file; methods that are internal to the class implementation can be omitted.

类实现

类的定义结构很像他的声明,以 @implementation 指令开始,@end结束

@implementation ClassName : ItsSuperclass
{
    instance variable declarations
}
method definitions
@end

但是,没个实现文件必须引入自己的接口,如,Rectangle.m引入 Rectangle.h,因为实现不需要重复任何他引入的声明,他可以安全的省略:

  • 基类名称
  • 实例参数声明

这简化了实现,使之更好的定义方法

#import "ClassName.h"
@implementation ClassName
method definitions
@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;
    }
    return twin;
}

实例变量作用域

虽然他们在类接口中声明了,但实例变量的实现方式比他的使用更重要,一个对象的接口在于它的方法,而不是它内部的数据结构

通常方法和实例变量一一对应,如:

- (BOOL)isFilled
{
    return filled;
}

但这不是必须的,有些方法返回不存在于实例变量中的信息,一些实例变量可能存储着这个对象不希望暴露的数据。

就如同类的一次次修正,变量的选择也可能变化,但是声明的方法仍然保留,只要消息是影响类实例的传播手段,这些变化都不会真正影响他的接口

为了强调对象隐藏他数据的能力,编译器限制了实例变量的作用域——也就是限制他们在程序中可见,但也提供了灵活性,使你明确的设置三种不同的作用域,每一作用域用一个编译器指令标记:

指令 作用
@private 实例变量只能在声明他的这个类中访问
@protected 可在声明他的类以及继承于他的类中可访问
@public 任何地方访问
@package 使用现代的运行环境,一个 @package 实例变量如同 @public  在类实现的映像之内,但是 @private 不在这与变量和函数的 private_extern 相似,任何在类实现映像之外的代码使用这个实例变量都会出错,这在架构类中的实例变量是最重要的,可能@private 太严格,而 @protected 和 @public太宽松

This is illustrated in Figure 2-1.


Figure 2-1  The scope of instance variables

这个指令后面是实例变量列表,直到另一个指令或者列表结束,下面的例子中, age, evaluation是私有,name,job和wage是保护,boss是公开

@interface Worker : NSObject
{
    char *name;
@private
    int age;
    char *evaluation;
@protected
    id job;
    float wage;
@public
    id boss;
}

默认是保护 @protected

所哟偶这个类声明的实例变量,不管是什么方式创建的,都在这个类定义的作用域中

- promoteTo:newPosition
{
    id old = job;
    job = newPosition;
    return old;
}

显然,如果一个类不能访问自己的实例变量,那这个变量没有任何用。

通常的,一个类也可以访问她所继承类的实例变量,引用变量的能力通常是可继承的,这使类可以在自己的作用域内看到整个的数据结构,特别是你希望一个类定义仅仅是她所继承的类的结果。 之前例子中的 promoteTo 方法就被定义在任何继承于  job 实例变量所属的 Worker 类的类。

但是,这些原因让你希望限制继承类直接访问实例变量:

  • 当一个子类访问一个继承实例变量时,声明这个变量的类依赖于他的实现部分,在以后的版本中,他无法去除这个变量,或者修改他扮演的规则而不影响子类
  • 此外,如果一个子类访问一个继承的实例变量,并修改了他的值,可能在不经意间引入了错误,特别是如果这个变量被类内部依赖时

要限制实例变量作用域,需要在这个类中声明,标记为 @private

At the other extreme, marking a variable @public makes it generally available, even outside of class definitions that inherit or declare the variable. Normally, to get information stored in an instance variable, other objects must send a message requesting it. However, a public instance variable can be accessed anywhere as if it were a field in a C structure. For example:

另一个极端,标记变量 @public 使之都可见
Worker *ceo = [[Worker alloc] init];
ceo->boss = nil;

Note that the object must be statically typed.

Marking instance variables @public defeats the ability of an object to hide its data. It runs counter to a fundamental principle of object-oriented programming—the encapsulation of data within objects where it’s protected from view and inadvertent error. Public instance variables should therefore be avoided except in extraordinary cases.

标记变量 @public 打破了对象隐藏他的数据,

self,  super 消息给自己和超类

Objective-C提供两个术语—— self和super,可以用于函数定义中,用于表示执行方法的对象

如:你定义了一个 reposition 方法需要改变任何他作用于的对象的坐标,他可以调用 setOrigin:: 方法去改变,他所要做的就是给 reposition 消息自己发送setOrigin:: 消息,当你写代码时,你可以用self或者super

- reposition
{
    ...
    [self setOrigin:someX :someY];
    ...
}

or:

- reposition
{
    ...
    [super setOrigin:someX :someY];
    ...
}

Here, self and super both refer to the object receiving a reposition message, whatever object that may happen to be. The two terms are quite different, however. self is one of the hidden arguments that the messaging routine passes to every method; it’s a local variable that can be used freely within a method implementation, just as the names of instance variables can be. super is a term that substitutes for self only as the receiver in a message expression. As receivers, the two terms differ principally in how they affect the messaging process:

  • self – 按通常的方式搜索方法,从接收者类的派发表格开始,上面的例子中,将从接收到这个 repostion 消息的对象类开始
  • super – 查找方法实现在不同的地方,他从定义这个方法的基类开始,上面的例子中,他从这个类的基类开始

Wherever super receives a message, the compiler substitutes another messaging routine for the objc_msgSend function. The substitute routine looks directly to the superclass of the defining class—that is, to the superclass of the class sending the message to super—rather than to the class of the object receiving the message.

An Example

The difference between self and super becomes clear in a hierarchy of three classes. Suppose, for example, that we create an object belonging to a class called Low. Low’s superclass is Mid; Mid’s superclass is High. All three classes define a method called negotiate, which they use for a variety of purposes. In addition, Mid defines an ambitious method called makeLastingPeace, which also has need of the negotiate method. This is illustrated in Figure 2-2:


Figure 2-2  High, Mid, Low

We now send a message to our Low object to perform the makeLastingPeace method, and makeLastingPeace, in turn, sends a negotiate message to the same Low object. If source code calls this object self,

- makeLastingPeace
{
    [self negotiate];
    ...
}

the messaging routine finds the version of negotiate defined in Low, self’s class. However, if Mid’s source code calls this object super,

- makeLastingPeace
{
    [super negotiate];
    ...
}

the messaging routine will find the version of negotiate defined in High. It ignores the receiving object’s class (Low) and skips to the superclass of Mid, since Mid is where makeLastingPeace is defined. Neither message finds Mid’s version of negotiate.

As this example illustrates, super provides a way to bypass a method that overrides another method. Here it enabled makeLastingPeace to avoid the Mid version of negotiate that redefined the original High version.

Not being able to reach Mid’s version of negotiate may seem like a flaw, but, under the circumstances, it’s right to avoid it:

  • The author of the Low class intentionally overrode Mid’s version of negotiate so that instances of the Low class (and its subclasses) would invoke the redefined version of the method instead. The designer of Low didn’t want Low objects to perform the inherited method.
  • In sending the message to super, the author of Mid’s makeLastingPeace method intentionally skipped over Mid’s version of negotiate (and over any versions that might be defined in classes like Low that inherit from Mid) to perform the version defined in the High class. Mid’s designer wanted to use the High version of negotiate and no other.

Mid’s version of negotiate could still be used, but it would take a direct message to a Mid instance to do it.

Using super

Messages to super allow method implementations to be distributed over more than one class. You can override an existing method to modify or add to it, and still incorporate the original method in the modification:

- negotiate
{
    ...
    return [super negotiate];
}

有些情况,每个继承的类可以实现函数的部分工作,余下的通过发消息给super完成(译者注:也就是子类重写父类的方法,通过super调用父类的这个方法的默认实现),init 方法也是这样,它可以用于初始化新的分配实例,每个init方法都有初始化这个类中定义的变量实例的义务,但是在这之前可以先通过super执行父类变量的初始化,每个类的init方法都遵循这样的流程,这样就可以保证类的所有实例变量按正确的顺序初始化

- (id)init
{
    self = [super init];
    if (self) {
        ...
    }
}

Initializer methods have some additional constraints, and are described in more detail in “Allocating and Initializing Objects.”

It’s also possible to concentrate core functionality in one method defined in a superclass, and have subclasses incorporate the method through messages to super. For example, every class method that creates an instance must allocate storage for the new object and initialize its isa variable to the class structure. This is typically left to the alloc and allocWithZone: methods defined in the NSObject class. If another class overrides these methods (a rare case), it can still get the basic functionality by sending a message to super.

重定义 self

super是一个简单的标记,告诉编译器从哪儿开始找方法去执行,他只能用在接收者接受消息,但 self是一个变量名,可以以任何方式使用,甚至被设置一个新值

There’s a tendency to do just that in definitions of class methods. Class methods are often concerned not with the class object, but with instances of the class. For example, many class methods combine allocation and initialization of an instance, often setting up instance variable values at the same time. In such a method, it might be tempting to send messages to the newly allocated instance and to call the instance self, just as in an instance method. But that would be an error. self and super both refer to the receiving object—the object that gets a message telling it to perform the method. Inside an instance method, self refers to the instance; but inside a class methodself refers to the class object. This is an example of what not to do:

+ (Rectangle *)rectangleOfColor:(NSColor *) color
{
    self = [[Rectangle alloc] init]; // BAD
    [self setColor:color];
    return [self autorelease];
}

To avoid confusion, it’s usually better to use a variable other than self to refer to an instance inside a class method:

+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[Rectangle alloc] init]; // GOOD
    [newInstance setColor:color];
    return [newInstance autorelease];
}

实际上,类方法中最好是发送消息给 alloc,这比self更好,使用这种方式,如果这个类是子类,rectangleOfColor消息被一个子类接收到,返回的实例将于这个子类相同(如NSArray的array方法继承于 NSMutableArray)

+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[self alloc] init]; // EXCELLENT
    [newInstance setColor:color];
    return [newInstance autorelease];
}

See “Allocating and Initializing Objects” for more information about object allocation.


四 × 3 =