对象, 类, 消息
本章描述了Objective-C语言里面实现和使用的对象,类和消息的基本原理。还介绍了Objective-C的运行环境(运行时)。
运行时系统
面向对象的C语言推迟了尽可能多的决策从编译和链接时间到运行时。只要可能,它动态的执行诸如创建对象和决定引用那个方法之类的操作。这意味着这种语言不仅仅需要一个编译器,并且需要一个运行时系统来执行编译过的代码。运行时系统为面向对象C语言但当了一种操作系统的功能;它是使语言工作的东西。但是通常情况下,你并不需要直接的和运行时交互。不过,要了解它提供的更多功能,请参考:Objective-C Runtime Programming Guide.
对象
顾名思义,面向对象的编程是基于对象的。一个对象通过独有的操作联系数据,因此用户可以使用或者改变这些数据。面向对象的C语言提供了一种不需要指定特定的类的对象的数据类型来定义一种对象变量-这允许动态类型。在程序里,你通常应该确保你所处理掉的对象已经不再需要了。
对象的基本原理
一个对象通过特有的操作来联系数据因此用户可以使用或者修改那些数据。在面向对象C语言里,这些操作被称为对象的方法,他们所改变的数据是对象的实例变量。本质上,一个对象捆绑数据结构(实例变量)和一组步骤(方法)为一个独立的编程单元。
比如,如果你正在编写一个绘图程序使用户可以创建由直线、圆、矩形、文字、和位图等等组成的图形,你可以为用户可以操作的各种基本形状创建类。例如,一个矩形对象,可以有实例变量来标识矩形在图上的位置以及他的宽度和高度。其他的实例变量可以定义矩形的颜色,是否被填充,和用来显示矩形的线条形状。一个矩形类应该有方法来设置实例的位置、大小、颜色、填充状态和线条类型,以及一个能使实例显示自己的方法。
在面向对象C语言里,一个对象的实例变量是在变量内部的;通常,你只能通过对象的方法来访问对象的状态(你可以指定子类或者其他的对象能通过范围指令来直接访问实例变量,参考:“The Scope of Instance Variables”)。为了使其他人能了解对象的信息,必须要有一个方法来提供这些信息。比如:一个矩形应该有方法能揭示它的大小和位置。
此外,一个对象只应该看见为他本身设计的方法;他不能错误的履行为其他类型的对象准备的方法。就像C函数保护他的局部变量一样,向其他的程序隐藏他们,一个对象隐藏了他的实例变量和他的方法实现。
id
在面向对象C语言里,对象的标识符是独特的数据类型:id.这是通用的样式对任何类型的类的对象。(它能被用在类的实例和类对象自己。)id
被定义成一个对象数据结构的指针:
typedef struct objc_object {
Class isa;
} *id;
所有的对象因此有一个isa
变量来告诉他们是哪种类的实例。
专业术语: 既然Class类型自身定义成一个指针: typedef struct objc_class *Class;
isa
变量经常被称为“isa
pointer.”(isa 指针)
就像C函数或者数组,对象是通过它的地址来识别的。所有的对象,无论他们的实例变量或者方法,都是id类型。
id anObject;
为了Objective-C的面向对象构架,比如方法返回值,id 代替了 int 作为默认的数据类型。(在严格的C构架里,比如函数返回值,int 仍然是默认类型。)
关键字 nil
被定义成一个空的对象,一个值为0的id. id, nil 和其他基本的类型在Objective-C里被定义在头文件:objc/objc.h
.
动态类型
id
类型是完全没有限制的。从它自身看来,它放弃了任何有关对象的信息,除了它是个对象。
但是对象并不全都一样。一个矩形不应该和表现为位图的对象有同样的方法和实例对象。从某种程度说,一个程序需要找到对象包含的更多细节信息—对象的实例变量是什么,它的方便能履行啥功能,等等。既然 id 类型标志符不能提供这些新奇给编译器,每个对象必须能够在运行时提供它。
isa
实例变量标志了对象的类 — 是那种类型的对象。每个矩形对象应该能告诉运行时系统这是一个矩形。每个圆能说明这是一个圆。有相同行为(方法)和相同类型的数据(实例变量)的对象是同样的类的不同成员。
对象因此在运行时是动态类型。只要需要时,运行时系统就能找到一个对象所属于的正确的类, 只要通过向对象请求。(想要了解更多有关运行时, 请参考:Objective-C Runtime Programming Guide.) 动态类型在Objective-C里时是动态绑定的基础,将在稍后讨论。
isa 变量也能使对象履行自省
— 找出它们自己(或者其他对象)。编译器记录了数据结构里有关类定义的信息来供运行时系统使用。运行时系统的函数们使用 isa,以便在运行时找到这个信息。使用运行时系统,你就能,比如,决定是否一个对象执行了特殊的方法或者找到它的父类的名字。
对象类们的更多信息被在这里讨论: “Classes.”
同样可以通过静态的在源代码中输入类名字来提供给编译器有关对象的类信息。类是特殊类型的对象,类名字可能被当做是一种类型名字。请参考:See“Class Types” and“Enabling Static Behavior.”
内存管理
在一个Objective-C程序里, 重要的一点事要确保销毁已经不再需要的对象—否则你的应用程序的内存占用将比必需的要大。另外一点同样重要的就是你不能销毁正在被使用中的对象。
Objective-C提供了两种环境来供你进行内存管理:
-
自动引用计数 (ARC),编译器来决定对象的声明周期。(译者注:5.0里新增的特性,还没能广泛运用起来。)
-
引用计数, 由你最终引用的地方来决定对象的生命周期。
具体的引用计数描述请参考: Memory Management Programming Guide for Cocoa.
-
垃圾回收, 从你传递引用的到一个自动的“收集器”来决定对象的生命周期。
垃圾回收机制在这里被描述: Garbage Collection Programming Guide. (iPhone上不可用—所以你在iPhone开发中心找不到该文档)
对象消息发送
这一节解释了发送消息的语法,包括你怎么样能够嵌套消息表达式。它还讨论了一个对象的实例变量的范围或者“可见性”,以及多态和动态绑定的概念。
消息语法
为了让一个对象做一些事情,你给他发一个 消息 告诉他应用一个方法。在Objective-C里, 消息表达式 是用方括号包括的:
[receiver message] |
上面的 receiver 是一个对象,message告诉他该做什么。在源代码里, message 仅仅是方法的名字,任何参数都是传递给他的。当发出一个消息以后,运行时系统从全部的接受者里选择合适的方法并且调用它。
例如,这个消息告诉 myRectangle
对象来执行它的 display
方法,这会导致矩形显示自己:
[myRectangle display]; |
这个消息后面跟着一个 “;
”,按照C语言里面的语句标准。
因为消息里面的方法名称用来“选择”一个方法执行,消息里的方法名字通常被称为 选择器.
方法也可以带参数。只有一个参数的消息在名字后面附加冒号(:
) ,冒号右边跟着参数:
[myRectangle setWidth:20.0]; |
对于有多个参数的方法, Objective-C的方法名和参数是混合在一起的,那样使得方法的名字自然的描绘了方法所期待的参数。下面这个虚假的消息告诉The imaginary message below tellsmyRectangle
对象去设置其原点坐标 (30.0, 50.0):
[myRectangle setOriginX: 30.0 y: 50.0]; // 这是一个多参数的良好例子 |
// |
一个选择器名字包含了名字的所有部分,包括冒号,所以之前例子里的选择器被命名为setOriginX:y: 它有2个冒号,因为带2个参数。但是选择器名字不包含任何其他的东西,比如返回类型或者参数类型。
注意: Objective-C 里面选择器名字的子部分不是可选的,顺序也不可以被改变。在某些语言里,术语“命名参数”和“关键字参数”携带的含义是参数在运行时是可变的,可以有默认参数,可以有不同的顺序,还可以额外的命名参数。这些参数有关的特性在Objective-C里面都不适用。
对所有的意向和目的,一个Objective-C的方法声明是一个简单的C函数预先添加了2个额外参数(see“Messaging” in theObjective-C Runtime Programming Guide). 因此,Objective-C的方法声明结构和使用命名或者关键字参数的语言不一样,比如Python,下面举一个Python的例子:
def func(a, b, NeatMode=SuperNeat, Thing=DefaultThing): |
pass |
在这个Python例子里,Thing和NeatMode可以被省略或者在被调用的时候有不同的值。
一般而言,一个矩形类可以用第2个参数没有标签的方法来代替执行setOrigin::
方法,将被像下面这样引用:
[myRectangle setOrigin:30.0 :50.0]; // 这个多参数的一个坏的例子 |
虽然语法上是合法的,setOrigin::
没有混合方法名字和参数。这样,第2个参数是没有标记的,有效,但是阅读这个代码的人比较南区判断这个方法的参数是为了哪种目的。
方法也有可能接受可变数量的参数,尽管这比较罕见。额外的参数用逗号分隔跟在方法名字的后面。 (不同于冒号,逗号并不是名字的组成部分。)在下面这个例子里,虚拟的makeGroup:
方法传递了一个必需的参数(group)和三个可选的参数:
[receiver makeGroup:group, memberOne, memberTwo, memberThree]; |
想标准的C函数一样,方法可以有返回值。下面的例子设置变量isFilled
为YES
如果 myRectangle
绘制的是实心矩形,或者NO
如果仅仅是在绘制边框。
BOOL isFilled; |
isFilled = [myRectangle isFilled]; |
注意变量和方法可以有同样的名字。
一个消息表达式可以嵌套到另外一个消息里。 在这里,矩形的颜色被设置成另外一个的颜色:
[myRectangle setPrimaryColor:[otherRect primaryColor]]; |
Objective-C 也提供了点 (.
) 操作符,提供了简洁方便的语法来调用对象的存取方法。点操作符通常用在和已经明的特性相关的地方(see“Declared Properties”) 详细描述在这里
发送消息到 nil
在Objective-C里, 可以发消息给nil
—只是在运行时没有任何影响而已。在Cocoa里面有很多模式利用了这个事实。消息的返回值是nil
也是正确的:
-
如果方法返回一个对象,那么发消息给
nil
返回0
(nil
). 例如:Person *motherInLaw = [[aPerson spouse] mother];
如果这里的
spouse
对象是nil
, 那么mother
被发送给nil
也返回nil
. -
如果方法返回任何指针类型,任何整数标量的大小小于或者等于
sizeof(void*)
, 一个float
, 一个double
, 一个long double
, 或者long long
, 那么一个消息发送给nil
返回0
. -
如果方法返回一个结构体,被 Mac OS X ABI Function Call Guide 定义了在寄存器里返回,那么发消息给
nil
返回0.0
给struct
里的每一个值。只有struct
的数据类型不会被填充成0
. -
如果方法返回不同于上述类型的任何类型,发送消息给
nil
的返回类型是未定义的。
下面的代码段距离说明了发送消息给 nil
有效的用法。
id anObjectMaybeNil = nil; |
|
// this is valid |
if ([anObjectMaybeNil methodThatReturnsADouble] == 0.0) |
{ |
// implementation continues... |
} |
接收者的实例变量
一个方法具有自动入口来访问接收对象的实例变量,你不需要通过参数来传递他们。例如:上面的 primaryColor
列举了没有参数的方法,然而他可以找到 otherRect
的原色并且返回。Every method assumes the receiver and its instance variables, without having to declare them as parameters.
This convention simplifies Objective-C source code. It also supports the way object-oriented programmers think about objects and messages. Messages are sent to receivers much as letters are delivered to your home. Message parameters bring information from the outside to the receiver; they don’t need to bring the receiver to itself.
A method has automatic access only to the receiver’s instance variables. If it requires information about a variable stored in another object, it must send a message to the object asking it to reveal the contents of the variable. The primaryColor
and isFilled
methods shown earlier are used for just this purpose.
See “Defining a Class” for more information on referring to instance variables.
Polymorphism
As the earlier examples illustrate, messages in Objective-C appear in the same syntactic positions as function calls in standard C. But, because methods “belong to” an object, messages don’t behave in the same way that function calls do.
In particular, an object can be operated on by only those methods that were defined for it. It can’t confuse them with methods defined for other kinds of object, even if another object has a method with the same name. Therefore, two objects can respond differently to the same message. For example, each kind of object that receives a display
message could display itself in a unique way. A Circle
and aRectangle
would respond differently to identical instructions to track the cursor.
This feature, referred to as polymorphism, plays a significant role in the design of object-oriented programs. Together with dynamic binding, it permits you to write code that might apply to any number of different kinds of objects, without you having to choose at the time you write the code what kinds of objects they might be. They might even be objects that will be developed later, by other programmers working on other projects. If you write code that sends a display
message to an id
variable, any object that has a display
method is a potential receiver.
Dynamic Binding
A crucial difference between function calls and messages is that a function and its parameters are joined together in the compiled code, but a message and a receiving object aren’t united until the program is running and the message is sent. Therefore, the exact method invoked to respond to a message can be determined only at runtime, not when the code is compiled.
When a message is sent, a runtime messaging routine looks at the receiver and at the method named in the message. It locates the receiver’s implementation of a method matching the name, “calls” the method, and passes it a pointer to the receiver’s instance variables. (For more on this routine, see “Messaging” in Objective-C Runtime Programming Guide.)
This dynamic binding of methods to messages works hand in hand with polymorphism to give object-oriented programming much of its flexibility and power. Because each object can have its own version of a method, an Objective-C statement can achieve a variety of results, not by varying the message but by varying the object that receives the message. Receivers can be decided as the program runs; the choice of receiver can be made dependent on factors such as user actions.
When executing code based upon the Application Kit (AppKit), for example, users determine which objects receive messages from menu commands such as Cut, Copy, and Paste. The message goes to whatever object controls the current selection. An object that displays text would react to a copy
message differently from an object that displays scanned images. An object that represents a set of shapes would respond differently to a copy
message than a Rectangle
would. Because messages do not select methods until runtime (from another perspective, because binding of methods to messages does not occur until runtime), these differences in behavior are isolated to the methods themselves. The code that sends the message doesn’t have to be concerned with them; it doesn’t even have to enumerate the possibilities. An application’s objects can each respond in its own way to copy
messages.
Objective-C takes dynamic binding one step further and allows even the message that’s sent (the method selector) to be a variable determined at runtime. This mechanism is discussed in the section“Messaging” in Objective-C Runtime Programming Guide.
Dynamic Method Resolution
You can provide implementations of class and instance methods at runtime using dynamic method resolution. See “Dynamic Method Resolution” in Objective-C Runtime Programming Guide for more details.
Dot Syntax
Objective-C provides a dot (.
) operator that offers an alternative to square bracket notation ([]
) to invoke accessor methods. Dot syntax uses the same pattern that accessing C structure elements uses:
myInstance.value = 10; |
printf("myInstance value: %d", myInstance.value); |
When used with objects, however, dot syntax acts as “syntactic sugar”—it is transformed by the compiler into an invocation of an accessor method. Dot syntax does not directly get or set an instance variable. The code example above is exactly equivalent to the following:
[myInstance setValue:10]; |
printf("myInstance value: %d", [myInstance value]); |
As a corollary, if you want to access an object’s own instance variable using accessor methods, you must explicitly call out self
, for example:
self.age = 10; |
or the equivalent:
[self setAge:10]; |
If you do not use self.
, you access the instance variable directly. In the following example, the set accessor method for age
is not invoked:
age = 10; |
If a nil
value is encountered during property traversal, the result is the same as sending the equivalent message to nil
. For example, the following pairs are all equivalent:
// Each member of the path is an object. |
x = person.address.street.name; |
x = [[[person address] street] name]; |
|
// The path contains a C struct. |
// This 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"]; |
注:本文章是直接翻译的苹果官方网页上的的资料,最新官方网页请参考
欢迎朋友们指导修改~~~~
转载请保留以下信息:
作者(Author):smilelance
出处( From ):http://blog.csdn.net/smilelance