Objective-C – 对象,类和消息

原文:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html
翻译:Sam Sha – ycoder.com

本章描述Objective-C中的对象,类和消息的原理使用和实现,还会介绍Objective-C的运行

运行器

从compile time 和 link time 到 runtime,Objective-C都尽可能遵循规范。只要有可能,它会动态的执行操作如创建对象,调用函数。这意味着这个语言不只是需要编译器,也需要运行系统去执行编译过的代码。运行系统扮演着Objective-C语言的操作系统的角色,它让语言工作。通常,你并不需要直接关心runtime,要了解更多相关功能,参阅 Objective-C Runtime Programming Guide.

对象

正如其名,面向对象程序都是围绕着对象而建立,一个对象关联数据,通过特定操作去使用和影响数据。Objective-C提供一个数据类型标示一个对象变量,无需指定这个对象的类型──这样就能允许动态输入

对象基础

一个对象通过特定操作使用和操作相关联的数据,Objective-C中这种操作是对象的函数,他所影响的数据是他的实例变量(在其他环境中,通过ivars或者函数变量来引用)。本质上,对象绑定了一个数据结构(实例变量)和一组过程(方法)到一个对立的编程单位。

在Objective-C中,对象的实例变量在对象内部,通常你只能通过对象的方法访问这个对象的状态(通过使用作用域指令,你可以指定子类或者其他对象是否允许访问实例变量,参阅“The Scope of Instance Variables”

此外,对象只能看到为他设计的方法,他不能错误的执行其他类型对象的方法,正如C语言保护他的本地变量,对其他程序隐藏那样,对象隐藏他的实例参数和方法的实现

id

Obective-C中,对象的唯一标示是一种特殊的数据类型:id, 适用于任何类型的对象甚至类。

id anObject;

在面向对象的Objective-C中,如方法返回值,id代替int作为默认数据类型(而在严格的C中,函数返回值,int还是默认类型)

关键词nil定义的是一个空对象,0.id, nil以及其他Objective-C中基本类型的id定义在头文件objc/objc.h中

id作为指针被定义在一个对象数据结构中

typedef struct objc_object {
    Class isa;
} *id;

所有对象有一个 isa 变量,告诉他们是什么类型的实例当类型是他自己时定义为指针

typedef struct objc_class *Class;

这个 isa 变量通常被称为” isa 指针”

动态类型

id 类型完全是非限制性的,它本身不产生任何对象相关的信息,它只是一个对象,有时候,程序常常需要找出更多这个对象的信息,但是这个 id 类型被设计为不提供任何信息给编译器,每个对象只在运行时需要提供。

isa 参数标示这个对象的类 —— 也就是这个对象是什么类型。拥有相同行为(方法)和相同数据成员的对象是同类型的对象

动态类型的对象,如果必要,运行系统可以找出对象拥有的确切类型,通过询问这个对象。(学习更多关于运行时,参阅:Objective-C Runtime Programming Guide.),Objective-C中的动态类型支持为动态绑定打下了基础,后面的章节会介绍

isa 变量也使对象执行introspection——查找他们自己或者其他对象。编译器记录数据结构的类定义信息用于运行系统使用。运行时,运行系统使用 isa 去查找这些信息,使用运行时系统,你可以,如决定一个对象是否实现一个具体方法或者查找基类名称。

对象类的更多描述见 “Classes.”

也可以提供编译器关于对象的类信息,通过类名在源代码中静态调用。类是特殊的对象,类名表示类型名称,参阅“Class Types” and “Enabling Static Behavior.”

内存管理

任何程序中,确保不被使用的对象的释放是很重要的——否则你的程序内存占用将必须要的更大,同样确保还在被使用的对象不被释放也很重要。

Objective-C提供两种内存管理环境,允许你实现这些目标:

对象消息

本章介绍发送消息的语法,包括如何嵌入消息表达式,还会描述对象实例变量的“可见性”和多态动态绑定的概念

消息语法

让对象去做某事,你发送一个消息告诉他去调用某个方法,在Objective-C中,消息表达式括在括号中:

[receiver message]

接收者是个对象,消息告诉他怎么作,源代码中,消息是方法名和任何传递参数,当消息发出,运行系统选择接收者的相应方法调用

举例,下面的消息告诉myRectangle对象执行他的 display 方法,用于显示这个矩形

[myRectangle display];

这个消息后面跟一个“;”如同通常的C语言代码

消息中的方法名称用于“选取”这个方法的实现,出于这个原因,消息中的方法名称通常作为选择器引用。

方法也可以带参数,如“arguments.”,消息中,单个参数附在冒号之后。

[myRectangle setWidth:20.0];

带多个参数的方法,Objective-C中的方法名与参数交错,如这个方法所期望的那样自然的描述参数,下面的消息告诉 myRectangle 对象设置位置在坐标(30.0, 50.0):

[myRectangle setOriginX: 30.0 y: 50.0]; // This is a good example of multiple arguments

选择器名称包括名称的所有部分,包括冒号,所一这个方法名是 setOriginX:y:,他拥有俩个冒号表示俩个参数,选择器名称不包括除此以外的任何东西,如返回类型和参数类型

理论上,Rectangle类改用 setOrigin:: 实现,调用如下:

[myRectangle setOrigin:30.0 :50.0]; // This is a bad example of multiple arguments

这种特殊的方法不间隔方法名和参数,因此,第二个参数没有标明,难以判断方法参数的类型和作用

方法也可以使用可变参数,虽然这很罕见,额外的参数用逗号分隔,(不像冒号,逗号不是方法名),下面的例子,假设 makeGroup 方法可传入一个必填参数和三个可选参数

[receiver makeGroup:group, memberOne, memberTwo, memberThree];

如同标准C语言,函数可以有返回值,下面的例子设置变量 isFilled为 yes 如果 myRectangle 被填充,no 如果只绘制边框

BOOL isFilled;
isFilled = [myRectangle isFilled];

注意变量和方法可以为相同的名称

消息表达式可以嵌套,这里,一个矩形的颜色设置到另一个。

[myRectangle setPrimaryColor:[otherRect primaryColor]];

Objective-C 也提供点(.)操作符,提供紧凑便捷的调用对象的访问函数。这通常配合声明属性使用(见“Declared Properties”),详见“Dot Syntax.”

发送消息给 nil

在Objective-C中,可以给 nil 发送消息,运行时不会有反应,Cocoa中的几种模式使用了这一优势,消息到nil的返回值可能有用

  • 如果方法返回一个对象,然后发送消息给这个nil,返回 0 (nil)
    Person *motherInLaw = [[aPerson spouse] mother];

    如果aPerson的配偶是空,然则配偶的母亲也是空

  • 如果方法返回任何指针类型,任何整数大小小于或者等于 sizeof(void*), float, double , long double 或者 long long ,发送到消息到 nil 返回 0
  • 如果方法返回一个结构,定义如Mac OS X ABI Function Call Guide 被返回到注册者,发送消息到 nil 访问数据结构的任何属性,都返回 0 ,其他结构的数据类型不会被填充 0
  • 方法返回上述以外的其他类型, 发送到 nil 的消息返回未定义

下面的代码说明可以给 nil 发送消息

id anObjectMaybeNil = nil;
// this is valid
if ([anObjectMaybeNil methodThatReturnsADouble] == 0.0)
{
    // implementation continues...
}

接收者实例变量

方法自动访问接收者对象实例变量,你不需要通过参数传入,如例,primaryColor方法说明可以不带参数,但他可以找到 otherRect 的初始颜色并返回,每个方法可以使用接收者和他的实例变量,而不需要如参数那样定义

这一规则简化了Objective-C源代码,他也有助于程序员以面向对象方式思考对象和消息,消息发送给接收者很像给你家里送信,消息参数携带外面的信息给接收者,他们不需要带上接收者本身。

方法只能访问接收者自己的实例变量,如果他需要另一个对象中的变量信息,他需要给这个对象发送消息询问这个变量 primaryColor和isFilled方法就是出于这样的目的

参阅 “Defining a Class” 了解更对实例变量引用的信息

多态

如上述例子说明,Objective-C中的消息与标准C中的函数调用具有相同的语法位置,但方法“属于”对象,消息行为与函数调用有区别

特别是,一个对象只能操作他定义的方法,不能与其他类型对象定义的方法混淆,即使另一个对象有相同名称的方法,这就是说两个对象可以有相同的消息响应不同的方法。如不同类型的对象发送 display 消息用不同的方式去显示自己,相同的指令圆形和矩形不同的响应跟踪光标。

动态绑定

函数调用与消息最主要的区别是,函数和他的参数一起打包在编译代码中,但消息和接收者对象直到程序运行消息发出才确定。因此一个消息的响应只有在运行时才决定哪个方法被调用,而不是在代码编译时。

消息调用的具体方法取决于接收者,不同的接收者可能有相同名称但不同实现的方法。

方法实现的选择在运行时,当消息发出,例行检查接收者,定位匹配名称的方法,调用这个方法,并传入一个指针给接收者实例变量(更多消息的信息,参阅:Messaging in the Objective-C Runtime Programming Guide.)

消息动态绑定方法与多态携手作用,为面向对象编程带来更多灵活和强大。每个对象有自己版本的方法,程序可以获取多种结果,不用改变消息本身,而是由于消息接收者的不同。这可以作为程序运行,接收者可以在“飞行中”做决定,可以由外部依赖,如用户动作。

当执行代码以应用程序包为基础时,用户决定那个对象接受消息如菜单命令剪切,复制,粘贴,消息将发送给当前选中的控件对象。同样的 copy 消息,显示文本和显示图片的对象有不同的响应。显示一堆图形和单个矩形有不同的响应。如果消息不在运行时选择方法(方法不一定要消息),区别是孤立的方法响应消息,发送消息的代码必须与方法关联,甚至没有枚举的可能性,每个程序可以创建自己的对象响应自己的 copy 消息。

Objective-C的动态绑定更进一步,允许在运行时发送消息的变化,详见  Messaging in the Objective-C Runtime Programming Guide.

动态方法机制

你可以在运行时提供类实现和方法实例,通过使用动态方法机制,见:Dynamic Method Resolution in the Objective-C Runtime Programming Guide for more details.

点(.)语法

Objective-C提供点操作符,支持紧凑便捷的语法用于替代方括号方式调用方法。尤其当你想访问或修改在另一个对象上的属性是

使用点语法

概要

你可以使用点语法调用与元素结构相同的方法

myInstance.value = 10;
printf("myInstance value: %d", myInstance.value);

点语法是单纯的“语法糖”——编译器会翻译成对应的方法(所以实际上你没有直接访问实例变量),示例代码与实际上等同下面的

[myInstance setValue:10];
printf("myInstance value: %d", [myInstance value]);

通常用途

使用点操作符你可以读或写属性,如下:

Listing 1-1  使用点语法访问属性

Graphic *graphic = [[Graphic alloc] init];
NSColor *color = graphic.color;
CGFloat xLoc = graphic.xLoc;
BOOL hidden = graphic.hidden;
int textCharacterLength = graphic.text.length;
if (graphic.textHidden != YES) {
    graphic.text = @"Hello";
}
graphic.bounds = NSMakeRect(10.0, 10.0, 20.0, 120.0);

(@"Hello" 是一个 NSString 对象 — 见 “Compiler Directives.”)

访问属性调用获取方法(默认是 property),设置调用 色他 方法,默认是setProperty。通过属性声明功能你可以改变这些方法(见“Declared Properties”) ,经管与看上去的相反,点语法的封装——实际上并不能直接访问实例变量

下面是编译后的状态,使用了方括号,与上面例子中等价

Listing 1-2  使用方括号访问属性

Graphic *graphic = [[Graphic alloc] init];
NSColor *color = [graphic color];
CGFloat xLoc = [graphic xLoc];
BOOL hidden = [graphic hidden];
int textCharacterLength = [[graphic text] length];
if ([graphic isTextHidden] != YES) {
    [graphic setText:@"Hello"];
}
[graphic setBounds:NSMakeRect(10.0, 10.0, 20.0, 120.0)];

更高级的点语法是,当写一个只读属性时,编译器会提示错误,这样最好,当你调用一个没有 set 方法的属性时,它会发出一个未定义方法警告,避免运行时出错

因为C语言的特性,意味着复合操作也很好的被支持,如下,你可以更新一个 NSMutableData 实例的长度属性:

NSMutableData *data = [NSMutableData dataWithLength:1024];
data.length += 1024;
data.length *= 2;
data.length /= 4;

等价于:

[data setLength:[data length] + 1024];
[data setLength:[data length] * 2];
[data setLength:[data length] / 4];

有一种情况,属性不能被使用,考虑下面的代码片段

id y;
x = y.z;  // z is an undeclared property

注意,y 没有类型化因而z属性未定义,有几种方式解释,因为类型不明确,所以语句会报属性未定义的错误,如果 z 被声明,且当前编译单元中只有一个 z 属性的声明,则它就不再含糊不清,如果这个 z 属性有多个声明,只要他们有相同的类型(比如BOOL类型),则他是合法的,但如果其中一个被声明为 readonly则可能出现歧义。

nil Values

如果获取属性时是一个 nil 值,则结果与给 nil 传递消息是一样的,如例:下面的几组代码都是等价的:

// each member of the path is an object
x = person.address.street.name;
x = [[[person address] street] name];
// the path contains a C struct
// will crash if window is nil or -contentView returns nil
y = window.contentView.bounds.origin.y;
y = [[window contentView] bounds].origin.y;
// an example of using a setter....
person.address.street.name = @"Oxford Road";
[[[person address] street] setName: @"Oxford Road"];

self

如果你想获取 self的属性,你必须显示的使用 self,如下:

self.age = 10;

如果你不是用self,你将直接访问实例的参数,下例中,这种获取方式不会调用到age属性

age = 10;

性能和线程

The dot syntax generates code equivalent to the standard method invocation syntax. As a result, code using the dot syntax performs exactly the same as code written directly using the accessor methods. Since the dot syntax simply invokes methods, no additional thread dependencies are introduced as a result of its use.

Usage Summary

aVariable = anObject.aProperty;

Invokes the aProperty method and assigns the return value to aVariable. The type of the property aProperty and the type of aVariable must be compatible, otherwise you get a compiler warning.

anObject.name = @"New Name";

Invokes the setName: method on anObject, passing @"New Name" as the argument.

You get a compiler warning if setName: does not exist, if the property name does not exist, or if setName: returns anything but void.

xOrigin = aView.bounds.origin.x;

Invokes the bounds method and assigns xOrigin to be the value of the origin.x structure element of the NSRect returned by bounds.

NSInteger i = 10;
anObject.integerProperty = anotherObject.floatProperty = ++i;

Assigns 11 to both anObject.integerProperty and anotherObject.floatProperty. That is, the right hand side of the assignment is pre-evaluated and the result is passed to setIntegerProperty: and setFloatProperty:. The pre-evaluated result is coerced as required at each point of assignment.

Incorrect Use

The following patterns are strongly discouraged.

anObject.retain;

Generates a compiler warning (warning: value returned from property not used.).

/* method declaration */
- (BOOL) setFooIfYouCan: (MyClass *)newFoo;
/* code fragment */
anObject.fooIfYouCan = myInstance;

Generates a compiler warning that setFooIfYouCan: does not appear to be a setter method because it does not return (void).

flag = aView.lockFocusIfCanDraw;

Invokes lockFocusIfCanDraw and assigns the return value to flag. This does not generate a compiler warning unless flag’s type mismatches the method’s return type.

/* property declaration */
@property(readonly) NSInteger readonlyProperty;
/* method declaration */
- (void) setReadonlyProperty: (NSInteger)newValue;
/* code fragment */
self.readonlyProperty = 5;

Since the property is declared readonly, this code generates a compiler warning (warning: assignment to readonly property 'readonlyProperty'). Because the setter method is present, it will work at runtime, but simply adding a setter for a property does not imply readwrite.

Classes

面向对象编程的基础是丰富多彩的对象,基于Cocoa框架的程序可能使用 NSMatrix 对象, NSWindow对象,NSDictionary, NSFont, NSText 以及其他很多。程序常常使用多个具有相同功能或者类型的对象 —— 如多个 NSArray或者 NSWindow

In Objective-C, you define objects by defining their class. The class definition is a prototype for a kind of object; it declares the instance variables that become part of every member of the class, and it defines a set of methods that all objects in the class can use.

The compiler creates just one accessible object for each class, a class object that knows how to build new objects belonging to the class. (For this reason it’s traditionally called a “factory object.”) The class object is the compiled version of the class; the objects it builds are instances of the class. The objects that do the main work of your program are instances created by the class object at runtime.

All instances of a class have the same set of methods, and they all have a set of instance variables cut from the same mold. Each object gets its own instance variables, but the methods are shared.

By convention, class names begin with an uppercase letter (such as “Rectangle”); the names of instances typically begin with a lowercase letter (such as “myRectangle”).

Inheritance

Class definitions are additive; each new class that you define is based on another class from which it inherits methods and instance variables. The new class simply adds to or modifies what it inherits. It doesn’t need to duplicate inherited code.

Inheritance links all classes together in a hierarchical tree with a single class at its root. When writing code that is based upon the Foundation framework, that root class is typically NSObject. Every class (except a root class) has a superclass one step nearer the root, and any class (including a root class) can be the superclass for any number of subclasses one step farther from the root. Figure 1-1 illustrates the hierarchy for a few of the classes used in the drawing program.

Figure 1-1  Some Drawing Program Classes

This figure shows that the Square class is a subclass of the Rectangle class, the Rectangle class is a subclass of Shape, Shape is a subclass of Graphic, and Graphic is a subclass of NSObject. Inheritance is cumulative. So a Square object has the methods and instance variables defined for Rectangle, Shape, Graphic, and NSObject, as well as those defined specifically for Square. This is simply to say that a Square object isn’t only a Square, it’s also a Rectangle, a Shape, a Graphic, and an NSObject.

Every class but NSObject can thus be seen as a specialization or an adaptation of another class. Each successive subclass further modifies the cumulative total of what’s inherited. The Square class defines only the minimum needed to turn a Rectangle into a Square.

When you define a class, you link it to the hierarchy by declaring its superclass; every class you create must be the subclass of another class (unless you define a new root class). Plenty of potential superclasses are available. Cocoa includes the NSObject class and several frameworks containing definitions for more than 250 additional classes. Some are classes that you can use “off the shelf”—incorporate into your program as is. Others you might want to adapt to your own needs by defining a subclass.

Some framework classes define almost everything you need, but leave some specifics to be implemented in a subclass. You can thus create very sophisticated objects by writing only a small amount of code, and reusing work done by the programmers of the framework.

The NSObject Class

NSObject is a root class, and so doesn’t have a superclass. It defines the basic framework for Objective-C objects and object interactions. It imparts to the classes and instances of classes that inherit from it the ability to behave as objects and cooperate with the runtime system.

A class that doesn’t need to inherit any special behavior from another class should nevertheless be made a subclass of the NSObject class. Instances of the class must at least have the ability to behave like Objective-C objects at runtime. Inheriting this ability from the NSObject class is much simpler and much more reliable than reinventing it in a new class definition.

Inheriting Instance Variables

When a class object creates a new instance, the new object contains not only the instance variables that were defined for its class but also the instance variables defined for its superclass and for its superclass’s superclass, all the way back to the root class. Thus, the isa instance variable defined in the NSObject class becomes part of every object. isa connects each object to its class.

Figure 1-2 shows some of the instance variables that could be defined for a particular implementation of Rectangle, and where they may come from. Note that the variables that make the object a Rectangle are added to the ones that make it a Shape, and the ones that make it a Shape are added to the ones that make it a Graphic, and so on.

Figure 1-2  Rectangle Instance Variables

A class doesn’t have to declare instance variables. It can simply define new methods and rely on the instance variables it inherits, if it needs any instance variables at all. For example, Square might not declare any new instance variables of its own.

Inheriting Methods

An object has access not only to the methods defined for its class, but also to methods defined for its superclass, and for its superclass’s superclass, all the way back to the root of the hierarchy. For instance, a Square object can use methods defined in the Rectangle, Shape, Graphic, and NSObject classes as well as methods defined in its own class.

Any new class you define in your program can therefore make use of the code written for all the classes above it in the hierarchy. This type of inheritance is a major benefit of object-oriented programming. When you use one of the object-oriented frameworks provided by Cocoa, your programs can take advantage of the basic functionality coded into the framework classes. You have to add only the code that customizes the standard functionality to your application.

Class objects also inherit from the classes above them in the hierarchy. But because they don’t have instance variables (only instances do), they inherit only methods.

Comments are closed.