声明:本文档仅为个人学习过程中顺手翻译之作,方便开发的同胞借鉴参考。如有觉得译的不好不到位的地方,欢迎指正,将及时做出更正
尽量尊重原文档,因为首次Objective-C,有些地方可能直译了没有注意该语言的专有词,希望指正。如需转载,请注明出处
我的编程环境:
IDE:XCODE4.3.1
OS: MAC OS X 10.7.4
文章来译自:http://developer.apple.com/
我的编程环境:
静态行为
本节阐述了静态赋予类型是如何工作的,并讨论了Objective-C 其它的一些特性,包括如何临时地停止它固有动态特性。
默认的动态行为
按照设计,Objective-C 对象都是动态实体。关于它们的大多数决定从编译时推向运行时。
-
对象的内存是在运行时,类或者类方法创建新的实例时动态分配的。
-
对象是动态赋予类型的。在源码中 (编译时), 任何对象变量都可以是
id
类型的,无对象的类是什么。一个id
变量(以及它的特定方法和数据结构)的实际所属类直到程序运行时才被确定。 -
消息和方法是动态绑定的,就像在 “Dynamic Binding.” 中看到的。一个运行时进程会将消息中的方法选择器与“属于接收器”的方法实现进行匹配。
这些特性赋予了面向对象编程极大的可伸缩性以及能量,但也要付出一些代价。特别是编译器无法检查 id
变量的准确类型(类). 要获得更好的编译时类型检查,并让代码有更好的可读性,Objective-C 允许我们使用一个具体的类名来为变量静态赋予类型而不是泛泛的使用一个 id
类型。Objective-C 还允许我们关闭它的一些面向对象特性,以便将一些操作从运行时转到编译时来做。
注意: 消息的方式比方法调用在某种程度上慢一些,但是这些性能消耗同实际执行的工作比起来微不足道。通过取消objc的灵活性而带来的性能变化只有通过分析工具诸如Shark 或 Instruments进行分析时才能感觉的到。
静态赋予类型
如果在一个对象声明的 id
处使用了一个指向一个类名的指针,例如
Rectangle *thisObject; |
编译器会将声明的变量的值限制为声明中指定的类的一个实例或者从该类继承的子类的一个实例。在上面的例子中, thisObject
只能是一个 Rectangle
类或其子类的对象。
静态赋予类型的对象跟声明为 id
类型的对象拥有相同的内部数据结构。类型并不会影响对象;它只会影响提供给编译器的有关该对象的信息量以及源码中有关对象的可读信息量。
静态赋予类型也不会影响对象在运行时的处理。为静态赋予类型的对象分配的类跟 id
类型创建实例的类是一样的。如果 Square
是 Rectangle
的子类,那么下面的代码也会创建一个拥有 Square
对象所有实例变量的对象,而不仅仅是 Rectangle
对象的实例变量:
Rectangle *thisObject = [[Square alloc] init]; |
发送到静态类型对象的消息也是动态绑定的,同发送给 id
类型对象是相同的。静态指定类型的接受者在运行时的消息处理中任然会进行精确类型的检测。 向 thisObject
对象发送 display
消息:
[thisObject display]; |
如上执行的是 Square
类中的display方法,而不是他的父类 Rectangle
中的方法。
作为提供给编译器额外的对象信息,静态类型比id类型提供了更多的可能:
-
在某些情况下,它可以进行编译时的类型校验。
- 它可以使对象摆脱相同名称的方法必须有相同参数类型和返回值的限制。
-
它允许你使用结构指针运算符(.运算符)直接访问一个对象的实例变量。
前两条会在下面章节讨论,第三条参见 “Defining a Class.”
类型检查
当使用静态类型时编译器可以在以下两种情况下提供更好的类型校验:
-
当向一个静态类型的接收者发送一个消息时,编译器可以确定接收者是否可以响应。如果接收者没有与消息中方法名相对应的方法编译器会发出一个警告。
-
当一个静态类型的对象被分配到一个静态类型的变量,编译器会判断他们的类型是否匹配。如果不匹配会发出一个警告。
若要赋值操作时避免警告,需要对象的类型同变量的类型相同或者为变量类型的子类。就像下面的例子所展示的:
Shape *aShape; |
Rectangle *aRect; |
|
aRect = [[Rectangle alloc] init]; |
aShape = aRect; |
这里 aRect
可以被分配给 aShape
,因为矩形是图形的一种—— Rectangle
类继承自 Shape。但是,如果两者的角色调换一下也就是说将 aShape
分配给 aRect
,编译器会生成一个警告,因为不是所有的图形都是矩形。 (可以参见 1-2, 该处展示了类的继承关系,其中包括 Shape
和 Rectangle
.)
当表达式中等号两边的任何一个对象的类型为 id
时都不会进行类型校验。静态类型的对象可以任意的分配给 id
类型的对象,或者 id
类型的对象也可以分配给任何一个静态类型对象。由于像 alloc
和 init
这样的方法会返回 id
类型的对象,所以编译器并不能确保为静态类型变量返回一个类型合适的对象。像下面这样为变量赋值虽然很容易出错误,但是仍然是被允许的:
Rectangle *aRect; |
aRect = [[Shape alloc] init]; |
返回类型和参数类型
通常情况下,不同类中的同名方法其参数和返回值类型也必须相同。这一限制由编译器强制执行以实现动态绑定。因为在运行时编译器无法知道一个消息接收者的类型(实现被调用方法的类),编译器必须对相同名字的方法相似对待。当编译器为运行时系统准备方法的信息时,它仅创建一个方法描述来应对每一个方法选择器。
但是,当向静态类型对象发送一个消息时,编译器知道接收者的类型。编译器可以取得关于这个特定类中方法的定义信息,因此消息的返回值和参数类型就不再受上面的约束。
为一个继承类赋予静态类型
一个类的实例可以被静态的制定为它本身类的类型也可以被指定为它所继承的类的类型。例如,所有的实例都可以被静态的指定为 NSObject
类型。
但是,编译器只是通过类型定义中的类名来确定类的静态类型并进行类型校验。因此为一个实例指定一个它所继承类的类型会使编译器所预期的处理同运行时实际的处理出现偏差。
例如,如果你把一个 Rectangle
实例指定为 Shape
类型:
Shape *myRectangle = [[Rectangle alloc] init]; |
编译器会认为它就是一个 Shape
实例。如果你向这个对象发送一个消息让其运行一个 Rectangle
中的方法:
BOOL solid = [myRectangle isFilled]; |
编译器会表示很愤怒。因为 isFilled
方法是在 Rectangle
类中定义的而不是在 Shape
中。
但是,如果发送一个消息,来运行一个 Shape
中的方法:
[myRectangle display]; |
编译器表示很淡定,即使 Rectangle
已经覆写了这个方法。但是在运行时,实际运行的方法是 Rectangle
版本中定义的方法。
类似,假如有一个 Upper
类声明了一个 worry
方法,它有一个 double
类型返回值:
- (double)worry; |
同时Upper
的子类 Middle
复写了这个方法并返回了一个新类型的返回值:
- (int)worry; |
如果一个实例被静态的指定为 Upper
类型,编译器会认为他的 worry
方法返回一个 double
类型返回值,同时如果一个实例被指定为 Middle
类型,编译器会认为 worry
应该返回一个int类型。如果一个Middle实例被指定为 Upper
类型编译器会通知运行时系统对象的 worry
方法返回一个 double
类型,但是在运行时实际的返回值为 int
类型同时会生成一个错误。
静态赋予类型可以使同名方法无须受到必须具有相同参数类型及返回值类型的约束,但是只有应用在不同继承关系中定义的方法,这样做才可靠。
英文原文:点击打开链接
Enabling Static Behavior
This chapter explains how static typing works and discusses some other features of Objective-C, including ways to temporarily overcome its inherent dynamism.
Default Dynamic Behavior
By design, Objective-C objects are dynamic entities. As many decisions about them as possible are pushed from compile time to runtime:
-
The memory for objects is dynamically allocated at runtime by class methods that create new instances.
-
Objects are dynamically typed. In source code (at compile time), any object variable can be of type
id
no matter what the object’s class is. The exact class of anid
variable (and therefore its particular methods and data structure) isn’t determined until the program runs. -
Messages and methods are dynamically bound, as described in “Dynamic Binding.” A runtime procedure matches the method selector in the message to a method implementation that “belongs to” the receiver.
These features give object-oriented programs a great deal of flexibility and power, but there’s a price to pay. In particular, the compiler can’t check the exact types (classes) of id
variables. To permit better compile-time type checking, and to make code more self-documenting, Objective-C allows objects to be statically typed with a class name rather than generically typed as id
. Objective-C also lets you turn off some of its object-oriented features in order to shift operations from runtime back to compile time.
Note: Messages are somewhat slower than function calls, typically incurring an insignificant amount of overhead compared to actual work performed. The exceptionally rare case where bypassing the dynamism of Objective-C might be warranted can be proven by use of analysis tools like Shark or Instruments.
Static Typing
If a pointer to a class name is used in place of id
in an object declaration such as
Rectangle *thisObject; |
the compiler restricts the value of the declared variable to be either an instance of the class named in the declaration or an instance of a class that inherits from the named class. In the example above, thisObject
can be only a Rectangle
object of some kind.
Statically typed objects have the same internal data structures as objects declared to be of type id
. The type doesn’t affect the object; it affects only the amount of information given to the compiler about the object and the amount of information available to those reading the source code.
Static typing also doesn’t affect how the object is treated at runtime. Statically typed objects are dynamically allocated by the same class methods that create instances of type id
. If Square
is a subclass of Rectangle
, the following code would still produce an object with all the instance variables of a Square
object, not just those of aRectangle
object:
Rectangle *thisObject = [[Square alloc] init]; |
Messages sent to statically typed objects are dynamically bound, just as messages to objects typed id
are. The exact type of a statically typed receiver is still determined at runtime as part of the messaging process. A display
message sent to the thisObject
object:
[thisObject display]; |
performs the version of the method defined in the Square
class, not the one in its Rectangle
superclass.
By giving the compiler more information about an object, static typing opens up possibilities that are absent for objects typed id
:
-
In certain situations, it allows for compile-time type checking.
-
It can free objects from the restriction that identically named methods must have identical return and parameter types.
-
It permits you to use the structure pointer operator to directly access an object’s instance variables.
The first two possibilities are discussed in the sections that follow. The third is covered in “Defining a Class.”
Type Checking
With the additional information provided by static typing, the compiler can deliver better type-checking services in two situations:
-
When a message is sent to a statically typed receiver, the compiler can make sure the receiver can respond. A warning is issued if the receiver doesn’t have access to the method named in the message.
-
When a statically typed object is assigned to a statically typed variable, the compiler makes sure the types are compatible. A warning is issued if they’re not.
An assignment can be made without warning, provided the class of the object being assigned is identical to, or inherits from, the class of the variable receiving the assignment. The following example illustrates this:
Shape *aShape; |
Rectangle *aRect; |
|
aRect = [[Rectangle alloc] init]; |
aShape = aRect; |
Here aRect
can be assigned to aShape
because a rectangle is a kind of shape—the Rectangle
class inherits fromShape
. However, if the roles of the two variables are reversed and aShape
is assigned to aRect
, the compiler generates a warning; not every shape is a rectangle. (For reference, see Figure 1-2, which shows the class hierarchy including Shape
and Rectangle
.)
There’s no check when the expression on either side of the assignment operator is of type id
. A statically typed object can be freely assigned to an id
object, or an id
object to a statically typed object. Because methods likealloc
and init
return objects of type id
, the compiler doesn’t ensure that a compatible object is returned to a statically typed variable. The following code is error-prone, but is allowed nonetheless:
Rectangle *aRect; |
aRect = [[Shape alloc] init]; |
Return and Parameter Types
In general, methods in different classes that have the same selector (the same name) must also share the same return and parameter types. This constraint is imposed by the compiler to allow dynamic binding. Because the class of a message receiver (and therefore class-specific details about the method it’s asked to perform), can’t be known at compile time, the compiler must treat all methods with the same name alike. When it prepares information on method return and parameter types for the runtime system, it creates just one method description for each method selector.
However, when a message is sent to a statically typed object, the class of the receiver is known by the compiler. The compiler has access to class-specific information about the methods. Therefore, the message is freed from the restrictions on its return and parameter types.
Static Typing to an Inherited Class
An instance can be statically typed to its own class or to any class that it inherits from. All instances, for example, can be statically typed as NSObject
.
However, the compiler understands the class of a statically typed object only from the class name in the type designation, and it does its type checking accordingly. Typing an instance to an inherited class can therefore result in discrepancies between what the compiler thinks would happen at runtime and what actually happens.
For example, if you statically type a Rectangle
instance as Shape
as shown here:
Shape *myRectangle = [[Rectangle alloc] init]; |
the compiler treats it as a Shape
instance. If you send the object a message to perform a Rectangle
method,
BOOL solid = [myRectangle isFilled]; |
the compiler complains. The isFilled
method is defined in the Rectangle
class, not in Shape
.
However, if you send it a message to perform a method that the Shape
class knows about such as
[myRectangle display]; |
the compiler doesn’t complain, even though Rectangle
overrides the method. At runtime, the Rectangle
version of the method is performed.
Similarly, suppose that the Upper
class declares a worry
method that returns a double
as shown here:
- (double)worry; |
and the Middle
subclass of Upper
overrides the method and declares a new return type:
- (int)worry; |
If an instance is statically typed to the Upper
class, the compiler thinks that its worry
method returns a double
, and if an instance is typed to the Middle
class, the compiler thinks that worry
returns an int
. Errors result if aMiddle
instance is typed to the Upper
class: The compiler informs the runtime system that a worry
message sent to the object returns a double
, but at runtime it actually returns an int
and generates an error.
Static typing can free identically named methods from the restriction that they must have identical return and parameter types, but it can do so reliably only if the methods are declared in different branches of the class hierarchy.