声明:本文档仅为个人学习过程中顺手翻译之作,方便开发的同胞借鉴参考。如有觉得译的不好不到位的地方,欢迎指正,将及时做出更正
尽量尊重原文档,因为首次Objective-C,有些地方可能直译了没有注意该语言的专有词,希望指正。如需转载,请注明出处
Objects, Classes, and Messaging
对象基础
面向对象编程是围绕对象构建的。一个对象相关的数据,某些操作运算便能影响该数据。在Objective-C中,这些运算被称作对象的方法。受方法影响的数据是它的实例变量。(在别的环境中,它们可能是指实例变量或者成员变量)。本质上来讲,一个对象将数据结构(实例变量)以及一组进程(方法)绑定到了自身的编程单元中。
id
Objective-C中,对象标示符是一种另类的数据类型:id。这种类型一种可以用于所有对象的泛型类型,忽略具体的类,并且可以用于一个类的实例以及类对象自身。
id anObject; |
对于Objective-C的面向对象构造, 方法返回值,id替代int作为默认数据类型。(对于严格的C来说,函数返回值,int仍然是默认类型)
关键字nil用于定义一个空对象,一个拥有值,0.id,nil,可以在objc/objc.h中找到其它类型的Objective-C基础类型定义。
下面的例子中id被定义为指向一个数据构造函数的指针。
typedef struct objc_object { |
Class isa; |
} *id; |
每个下面这种类型对象都有一个isa变量,该变量可以告诉我们它是哪个类的实例。因为Class类型自身被定义成了一个指针。
typedef struct objc_class *Class; |
isa
通常被成为“isa
指针.”
动态类型
id类型是完全不严格的。就它自身来说,它没有产生任何有关一个对象的信息,当然除了它是一个对象。从某种意义上来说,一个程序通常需要得到更多有关一个对象它说包含的特定信息。因为id类型标示符无法为编译器提供这种特定信息,这些对象必须在运行时能够提供出这些信息。
isa实例变量知名了对象的类,该对象是什么类型的对象。拥有相同的行为(方法)以及相同的数据(实例变量)的对象是属于相同的类。
这种对象是运行时的动态类型。只要需要,运行时系统随时都可以找到该对象所属的准确的类,只要通过询问该对象。(要想了解关于运行时的信息,可以看考Objective-C Runtime Programming Guide一文)。Objective-C中的动态类型是作为动态绑定的基础进行服务的,晚些时候我们会进一步进行讨论。
isa变量同样可以让对象进行自省操作--以找到关于它们自己的信息(或者其它对象)。编译器会记录供运行时系统使用的有关类在数据结构中的定义相关信息。使用运行时系统时,你可以决定是否一个对象实现了一个特定的方法或者查找它的父类的名字。
我们将在“Classes”一章详细讨论对象类。
它还可以通过静态的在源代码中输入类名来为编译器提供对象类的信息。类是特定类型的对象,并且类名是作为类型名来使用的。可以参考“Class Types”和 “Enabling Static Behavior.”
内存管理
在任何程序中,当对象不在使用时确保它们被正确释放都是极其重要的,否则你的应用的内存轨迹会超出它们实际需要的量。同样重要的是,我们应当确保不要去释放正在使用中的对象。
Objective-C 提供了三种方式来让我们实现上述目标:
-
Automatic Reference Counting (ARC), 自动引用统计,编译器会推断出所有对象的生命周期
-
Manual Reference Counting (MRC), 手动引用统计,有时也叫MRR,即手动保持,释放。用于你完全掌控并决定对象的生命周期。
-
Garbage collection, 垃圾回收,将决定对象生命周期的责任传递给自动回收器
可以参考Garbage Collection Programming Guide. (但文档不适用于iOS—IOS的你可以通过访问IOS开发中心获取该文档.
对象消息
本节主要介绍了发送消息的语法,包括如何嵌套消息异常。我们还将讨论有关一个对象实例变量的可见域方面的问题。以及多态和动态绑定的概念。
发消息的语法
要让一个对象去做一些事情,你需要给它他送一个消息,告诉它去请求一个方法。在Objective-C中,消息异常被放在方括号中;
[receiver message]
receiver是一个对象,并且消息告诉它该驱做什么。在源代码中,小时仅是一个方法的名字以及需要传递给它的参数。当消息发送后,运行时系统会从receiver的指令系统中选择恰当的方法并调用它。
例如,下面这条消息告诉
myRectangle
对象执行它的display
方法, 这个方法会让这个矩形显示它自己;[myRectangle display];
消息后面会有一个分号 “
;
” 就像任何C中的语句一样。因为消息中的方法名用于选择实现方法, 在消息中的方法名通常也被称为选择器( selectors)。
方法可以带参数。单个参数的消息,参数后面会有一个冒号(
:
) ,冒号后面是参数的值[myRectangle setWidth:20.0];
对于有多个参数的方法, 形式类似单个参数,多个参数间用空格分开,格式也是 参数名:参数值
[myRectangle setOriginX: 30.0 y: 50.0]; // This is a good example of
// multiple parameters
选择器的名字包含名字的所有部分,包括冒号, 如上面看到的例子那样,当然还可以包含任何其它需要的,比如返回值类型以及参数类型
注意 Objective-C 选择器名的字部分不是可选的,它们的顺序也不能改变。在一些其它的语言中, “named parameters” 以及 “keyword parameters” 在运行时可以改变,可以拥有默认值,可以有不同的顺序,并且可能有额外的命名参数。这些特性Objective-C是没有的.无论怎么说,一个 Objective-C 方法声明只是一个简单的带有两个额外参数的C函数 (可以参考Objective-C Runtime Programming Guide文档中的 “Messaging” 获取更多信息). 因此, Objective-C 方法声明的结构不同于那些使用named 或者 keyword 参数的语言,比如Python, 就像下面的 Python 例子所描述的:
def func(a, b, NeatMode=SuperNeat, Thing=DefaultThing):
pass
Thing
和NeatMode
可能在被调用时被忽略或者有不同的值在日常操作中,
Rectangle
类可以简单的替代实现setOrigin::
方法,而没有第二个参数的任何标签,调用的时候如下面的例子:[myRectangle setOrigin:30.0 :50.0]; // This is a bad example of multiple parameters
语法上是合法的,
setOrigin::
没有将方法名与参数交叉。因此第二个参数没有任何标签,但这样会让看这段代码的人很困惑,不知道这些参数是做什么的。一个方法有可变参数也是可能的,尽管这种情况很少出现。额外的参数用逗号分开。在下面的例子中
makeGroup:
方法传递了一个必须的参数 (group
) 一起另外三个可选的参数:[receiver makeGroup:group, memberOne, memberTwo, memberThree];
类似标准的C函数,方法可以有返回值。
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];
如果这里的
spouse
对象是nil
, 那么mother
会发送到nil
并且方法会返回nil
. -
如果方法返回任何指针类型, 任何整型的表两只大小会小雨或者等于
sizeof(void*)
, 一个float
,double
,long double
, 或者long long
, 那么一条消息发送到nil
将返回0
. -
如果方法返回一个
struct
, 就像 Mac OS X ABI Function Call Guide 定义的那样,从寄存器中返回,那么一条消息发往nil
将对于每个struct
中的成员返回0.0
, 其它struct
数据类型不会被填充没0 -
如果方法返回任何前述的值类型,那么发往
nil
消息的返回值将是undefined.
The following code fragment illustrates a valid use of sending a message to
nil
.id anObjectMaybeNil = nil;
// this is valid
if ([anObjectMaybeNil methodThatReturnsADouble] == 0.0)
{
// implementation continues...
}
Receiver的实例变量
一个方法可以自动访问获取到的对象的实例变量。你不必把它们当做参数传给方法。例如上面提到过的
primaryColor
方法没有带任何参数,它还是为otherRect
找到了主颜色并将其返回. 每个方法都采用receiver 以及它的实例变量,无需将它们声明为参数。这个约定简化了Objective-C 代码. 它也支持面向对象编程的程序员所想的对象及消息。消息传给receivers就好像信件邮寄到你的家里。消息从外部携带着参数给 receiver; 它们没必要再将receiver传给它自己.
一个方法只可以自动访问获取到receiver的实例变量。如果它要求其它有关存储在另一个对象的变量信息,那么它必须发送一条消息给哪个对象,向它请求以获取该变量的内容。上面提到的
primaryColor
和isFilled
方法就是处于这个目的使用的。需要获取更多关于实例变量的信息,可以参考 “Defining a Class”
多态
像上面一些例子描述的,在Objective-C中的消息展现出来的功能就像标准的函数调用。但是,因为方法是 “属于” 一个对象, 消息并不需要像一个传统的函数调用那样行事。
通常,一个对象可以操作我们为其设计好的方法。为其它对象设计的方法不能迷惑它们,即便方法名相同。但是,两个对象可以对同一个消息做出不同的应答。例如,每个对象在接收到
display
消息时都可以以其独有的方式展现自身。这个特性,被称为多态,在面向对象编程中扮演着很重要的角色。同样重要的还有动态绑定,它使得我们写的代码可以应用到许许多多不同的对象中,无需在你写这些代码时就选择对象的类型是什么。还可能是应用到晚些时候才开发的对象上,被其它程序猿用在别的项目中。如果你写的代码发送了一个
display
消息给id
变量,任何拥有display
方法的对象都称为潜在的接收者.动态绑定
在函数调用跟消息间有个很显著的区别,就是函数以及它的阐述在编译好的代码中是结合在一起的。但消息以及它的接收对象并不结合在一起,知道代码开始运行并且消息被发送。因此,具体的用于回应消息的方法调用可以在运行时决定,而非在编译时及决定好。
当消息发送后,一个运行时消息例程会查看接收器以及消息中的方法名。它会定位匹配接收器的方法实现并“调用”这个方法,给它传递一个指向接收器实例变量的指针。(要了解更多关于这个例程的信息,可以参考 Objective-C Runtime Programming Guide中的 “Messaging”一节)
消息的动态绑定方法与多态特性赋予了面向对象编程强大的可伸缩性。因为每个对象都有它们自己的方法,一条Objective-C 语句可以得到各种结果,并非通过改变消息,而是靠改变接收消息的对象。接收器可以在程序运行时动态决定。接收器可以根据用户的动作等做出决定。
当执行基于Application Kit (AppKit)的代码时,例如,用户决定哪个对象需要从菜单命令中接收消息,如剪切,复制以及粘贴等。消息会找到当前正控制当前选项的对象。一个用来显示文本的对象与一个用来显示扫描图像的对象会对复制这个消息产生不同的反应。因为消息直到运行时才会选择所需的方法(换句话来讲,因为消息绑定的方法只有运行时才会执行), 这些行为上的不同让不同方法区别开来。发送消息的代码不用关注它们。它甚至不必猜测可能性。一个应用的对象都能以其队友的方式对复制这个消息做出回应。
Objective-C 让动态绑定更进的一步,并且甚至允许被发送给变量的消息运行时才决定。至于如何实现,我们将在Objective-C Runtime Programming Guide中的 “Messaging”一节进行讨论。
动态方法方案你可以在运行时使用动态方法放来提供类和实例方法的实现。具体可以参考Objective-C Runtime Programming Guide 的“Dynamic Method Resolution”一节。
逗点语法
Objective-C 提供了逗点 (
.
) 操作符,它可以为我们提供另外一种具有传统的方括号([])的功能的方式来访问方法。用法与C中类似myInstance.value = 10;
printf("myInstance value: %d", myInstance.value);
下面的例子与上例等效
[myInstance setValue:10];
printf("myInstance value: %d", [myInstance value]);
因此,如果你要通过使用访问方法来访问一个对象自己实例变量,你需要显示的调用self例如:
self.age = 10;
等价于
[self setAge:10];
如果在属性传递中产生了一个
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.
// 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"];
类
面向对象编程通常都是围绕着一系列对象进行构建。一个基于 Cocoa 框架的程序也许会用到对象:
NSMatrix
,NSWindow
,NSDictionary
,NSFont
,NSText
, 以及更多其它的对象。编程通常会用到不止一个类似的对象或者类,比如几个NSArray
对象或者NSWindow
对象。在Objective-C中, 你通过定义类来定义一组对象。类的定义是对一组某种对象的协议。它声明的实例变量称为所有类变量的一部分,并且它定义了一组所有类中变量都能使用的方法。
编译器仅诶每个类创建了一个可以访问的对象,一个类对象,这个类对象知道如何创建属于该类的新对象。(正因为这个,它通常被称为工厂对象)。这个类对象是类编译后的版本。由它创建的对象都是该类的实例。运行时干着你程序大多数活的对象都是类对象的实例。
所有类的实例都有相同的一套方法,并拥有从同一个模子得到的一套实例变量。每个对象获取到的是它们自己的实例变量,但方法是共享的。
按照惯例, 类名都是以大写字母开头, (例如
Rectangle
); 实例名则通常以小写字母开头(例如myRectangle
).继承
类的定义都是累加性的;每个你定义的新类都是基于两外一个类,并从那里继承方法与实例变量。新的类简单的添加或者修改继承的东西。继承来的东东则不必再重复写。
Inheritance 继承将所有类联系在一个继承树中,顶端是一个类。当基于基础框架写代码时,根类通常都是
Figure 1-1 Some drawing program classesNSObject.
每个类 (根类除外) 都有父类,比离根类更近,并且所有类 (包括根类) 都可以成为任意多个子类的父类,当然跟根类更远。图 1-1 描述了用于绘图的几个类的继承关系图 1-1 显示
Square
类是Rectangle
类的一个子类,Rectangle
类是Shape
类的一个子类,Shape
又是Graphic
类的一个子类,Graphic
是NSObject
的子类。继承是累加的。所以一个Square
对象拥有Rectangle
,Shape
,Graphic
, 以及NSObject
的方法跟实例变量。所以,我们可以说一个Square
对象不仅是一个正方形,同样也是一个矩形,形状,图形以及NSObject
的对象
除了
NSObject
以外的每个类都可以视为另外一个类的属性或者适配器。每个继承的子类进一步修改累积继承的总量。Square
类仅定义了仅够将一个举行转化为一个形状所需的东西。当你在定义一个类时,你需要通过声明它的父类把它关联到继承树中;每个你创建的类必须是其它某个类的子类,(除非你定义了一个新的根类). 大量潜在的子类是可能的。 Cocoa 包含
NSObject
类,许多框架包含了多余250个扩展类。有些类可以用于跟你的程序交互。其它的你可以希望按照你的实际需要创建该类的适配器子类。一些框架类定义了几乎你要用到的一切,但预留了一些特性让子类来实现。你就可以写几行代码就可以重用其它程序猿完成的框架代码了。
NSObject类
NSObjec
t
是一个根类,没有父类。它为Objective-C 对象以及对象的交互定义了基本框架. 它赋予了那些继承自它的类以及类的实例能够作为对象一样运行并与运行时系统进行合作互动的能力。如果一个类不想从另外一个类继承任何特定的动作,它任然还是
NSObject
类的子类. 该类的实例必须至少拥有运行时像一个Objective-C 对象的能力。继承自NSObject
类的这种能力简单但比从其它任何新类中改造的定义更加可靠。注意: 实现一个新的根类是一项让人头疼的任务,并且可能有许多致命的问题。这个类要重复许多
NSObject
类现在所做的事情, 例如分配实例,将它们与它们的类连接,并在运行时系统中识别它们。因此,你应当使用Cocoa提供的NSObject
类来作为你的根类. 更多信息可以参考 NSObject Class Reference 以及 NSObject Protocol Reference.继承实例变量
当一个类对象创建了一个新的实例,这个新的对象将不仅包含它所属类的实例变量,而且还包括为它的上层父类,上上层父类,上上上层。。。直到根类. 因此定义在NSObject的
isa
实例变量将称为所有对象的一部分。isa
将每个变量与它们的类联系在一起。图 1-2 向我们战士了几个实例变量,它们可以定义在一个特定的
Figure 1-2 Rectangle instance variablesRectangle
类的实现中。要注意的是,这些让一个对象成为矩形的变量们被加到能让其成为一个形状的对象中,而能使其成为形状的变量们又被加到了能让其成为图形的变量中。。。依此类推。并不是每一个类都需要声明实例变量。它可以简单的定义一些新方法并在需要实例变量时使用它继承的类实例变量。例如,
Square
肯能从来都没有过它自己的实例变量继承方法
一个对象不仅可以访问它自身类的方法,还可以访问父类的方法,以及。。。直到继承树的根类中的方法. 比如,一个
Square
对象可以使用来自以下类中的方法:Rectangle
,Shape
,Graphic
, 以及NSObject
也可以使用它自己类中的方法。任何一个你程序中新定义的类都可以利用写在继承树中的所有父类中的代码。这种类型的继承是面向对象编程的主要好处之一。当你使用Cocoa提供的面向对象框架时,你的程序就可以使用这些框架类提供的基础功能。你只需为你的应用对基础实现的个性化定制代码到你的应用中就O了。
类对象同样继承自继承树中它们的父类。但因为它们并没有实例变量(只有实例才有),它们只继承方法。
方法重写
继承有一个有用的例外:当你定义一个新类的时候,你可以实现一个新的方法,与继承树某个父类中定义的方法名相同。新方法会覆盖老方法。这个新类的实例会执行新的方法,而非父类中的同名方法,这个新类的子类也会继承这个新的方法而非老的方法。
例如:
Graphic
定义了一个display
方法,而Rectangle
重写并定义了它自己版本的display
方法。Graphic
的方法对所有继承自Graphic
类的对象都是可用的,但Rectangle
的对象则不同, 它将执行自己版本的display
方法。尽管重写一个方法会阻断原始版本的继承,但定义在新类的中的其它方法可以跳过被重新定义的方法,并找到原始的方法。(具体方法可以参考 “Messages to self and super”).
一个被重定义的方法也可以与被覆写的方法结合使用。
尽管子类可以重写它继承的方法但无法重写继承的实例变量。因为对象拥有继承时分配给每个实例变量的内存信息,你不能通过声明一个拥有同样名字的实例变量来重写继承来的变量。如果你真这么干了,编译器就会冲你使鬼脸。
抽象类
一些类被设计仅仅或主要用来让其它类继承它们。这些抽象类的方法和实例变量可以被许多的子类来使用。抽象类通常不会完全实现自己的方法,但包含一些很有用的代码,可以减少实现它们的子类的障碍与麻烦。(因为抽象类需要有子类才有用,所以它们有时也被称为抽象父类)
不像其它一些语言, Objective-C 没有专门的语法来把一个类标示为一个抽象类,也没有阻止你去创建一个抽象类的实例。
NSObject
类就是Cocoa中一个抽象类的典型例子。 你绝对不要在一个应用中使用NSObject
类的实例—因为这可不是一个好主意; 因为它只是一个对象,撒也干不了,唯一的用处是哪天让你自己困惑,告诉自己,“噢,该死,这是哪个蠢货在这里为一个抽象类创建了一个实例,难道是自己么”NSView
类, 提供了另外一个抽象类的例子,但你可能偶尔会用到它的实例。抽象类通常包含用来定义一个应用结构的代码。当你为这些类创建子类时,你的新类的实例就可以很好的符合应用结构的要求并与其它对象以其有爱的以其工作。
类的类别
一个类的定义是为了一个特定类型的对象的特性的集合。类实际上定义了数据类型,类型是不仅仅是基于类定义(实例变量)的数据类型,同样包括行为的定义。(方法).
只要是C中允许的形式,类名都可以出现在代码中。—例如, 作为
sizeof
操作符的参数:int i = sizeof(Rectangle);
静态类型
你可以在id的位置使用一个类名来指定对象的类型:
Rectangle *myRectangle;
因为这种声明一个对象的方法告知了编译器有关这个对象类型的信息,它被称作静态类型。因为id实际上是个指针,对象被静态的作为指针赋予了一个类。对象总是通过指针类指定类型。静态类型使 指针显示出来;id将它隐藏了。
静态类型允许编译器做一些类型检查—例如,当一个对象收到一个看起来它不能相应的消息时给予警告。—并且放宽对id类型对象使用的严格限制. 另外,对其它想要阅读你代码的人来说,它可以让你的意图更加清晰。但是,它并不能替代或者战胜动态绑定特性在运行时动态决定接收器的类的优点。
一个对象可以为它自己的类或者继承来的父类静态的赋予类型,例如, 因为继承使
Rectangle
对象同时也是一个Graphic
对象 (就像图 Figure 1-1中展示的那样), 一个Rectangle
实例也可以静态的赋予Graphic
类型:Graphic *myRectangle;
为父类静态的赋予类型在这里可以行得通是因为
Rectangle
对象同样也是Graphic
对象. 远不止这些,因为它还有Shape
和Rectangle
对象的实例变量以及方法能力, 但一个Graphic
对象就不行. 处于类型检查的目的,编译器认为myRectangle
的类型是Graphic
. 但运行时,如果运行时,myRectangle
被分配并初始化为Rectangle
的一个实例,它们会被让成相同的一个对待。静态赋予类型以及它的好处可以参考 “Enabling Static Behavior”
类型自省
实例可以在运行时获取到它们的类型。定义在
NSObject
类中的isMemberOfClass
方法, 会检查接收器是否是某个特定类的实例。if ( [anObject isMemberOfClass:someClass] )
...
同样定义在
NSObject
类中的isKindOfClass:
方法, 则会检查接收器是否继承自某个类或者是否是某个类的一员 (是否在继承树路径中):if ( [anObject isKindOfClass:someClass] )
...
对于那些类在方法isKindOfClass返回YES的类,那么这些接收器可以被静态的赋予类型。
自省不仅局限于类型信息。再后面的小节中会讨论返回类对象的方法,报告是否一个对象能对一个消息进行响应,并获取其它信息。
有关方法
isKindOfClass:
,isMemberOfClass:
,以及相关方法的更多信息 NSObject Class Reference类对象
一个类的定义包含很多种信息,大多数是关于类的实例的:
-
类名以及它的父类
-
用于描述一组实例变量的模板
-
方法名,返回类型,以及参数类型的声明
-
方法实现
这些信息将被编译并记录在数据结构中,并在运行时可用。编译器只会创建一个对象----一个类对象,代表这儿类。类对象可用访问有关这个类的所有信息,也就是说有关这个类的实例的是什么样的这类主要信息。类对象可用根据类中的定义产生新的实例。
尽管一个类对象持有一个类实例的协议,但它自身并不是一个实例。它自身也没有实例变量,但是,在类的定义可用包含为类对象使用的方法,类方法,有别于实例方法。一个类对象会从继承树中继承树中父类中的类方法。就像实例会继承实例方法一样。
在源代码中,类名代表着类对象。在下面的例子中
Rectangle
类返回了该类的版本号,使用的是从NSObject
类中继承的方法:int versionNumber = [Rectangle version];
但是,类名代表的类对象仅作为消息表达式里的接收器。其它地方,你需要请求实例或者类返回类
id
. 两者都返回class
消息:id aClass = [anObject class];
id rectClass = [Rectangle class];
像这些例子展示的,类对象可用像其它对象那样,赋予id类型。但类对象可用更特殊的被赋予
Class
类型。Class aClass = [anObject class];
Class rectClass = [Rectangle class];
所有类对象的类型都是
Class
.未完还在整理中。。。
类对象是一种成熟完备的对象,可以动态的赋予类型,接收消息,并从其它类继承方法。它们的独特之处仅在于它们是有编译器创建的。缺乏它们自己的数据结构(实例变量)而非类的生命中构建的,运行时类变量还是用来生成实例的代理。
注意: 编译器也会为每个类构建元类对象。元类对象能够描述类对象,就像类对象可以描述类的实例一样。但是当你给实例与类变量发送消息的时候,元类对象仅为运行时系统内部使用。
创建实例
类对象的一个主要功能就是穿件新的实例。下面的代码告诉
Rectangle
类去创建一个新的矩形实例,并将其赋给myRectangle
变量:id myRectangle;
myRectangle = [Rectangle alloc];
alloc方法会为新对象的实例变量分配一块内存并把它们都初始化为0----这里说的它们不包括用于连接新实例与它所属类的isa变量。要让一个对象更有作用,通常需要更完好的初始化。而这就是init方法的职能。初始化的工作一般会紧随分配工作之后进行。
myRectangle = [[Rectangle alloc] init];
在myRectangle 能够接收任何前面章节提到的消息之前,需要先执行上面的那行代码。
alloc
方法返回了一个新的实例,接着那个实例执行了一个init
方法以设置其初始化的状态。每个类对象都有至少一个方法(比如alloc
) 使其能够创建新的对象,并且每个实例都有至少一个方法(比如init
) 使其准备好以备使用。初始化的方法通常会带一些参数以传一些特定的值,当然还有关键词作为参数名。 (initWithPosition:size:
, 例如这个, 它是一个可以初始化一个新的Rectangle实例的方法
), 但每个初始化方法都是以 “init
”开头。定制类对象
对于Objective-C 语言磊说累呗当做对象来对待可不是什么突发奇想的事情。它是一种有计划的,有时候让人觉得不可思议,对设计来说获益颇多。我们可以为一个类定制一个对象,该类属于一个开放的集合。在 AppKit里, 举个例子, 一个
NSMatrix
对象可以用一个特定类型的NSCell
对象进行定制。NSMatrix
对象可以负责创建可以代表其单元的独特对象。它可以在matrix是第一次初始化,并在晚些时候需要新的单元时执行这个操作。NSMatrix
对象在屏幕上绘制的可见的 matrix在运行时是可以伸缩的,或许是对用户的操作的响应。当它扩展时,matrix需要能够创建新的对象来填充新加的位置。但它们是什么样的对象呢?每个matrix只显示一种
NSCell
, 但有很多种不同的类型。在图 1-3 的继承树中向我们展示了AppKit.中提供的其中一些类型。所有的都继承自NSCell
类.
图 1-3 NSCell 的继承树当一个matrix创建了
NSCell
对象, 应该是NSButtonCell
对象来显示一块按钮或者切换按钮,还是NSTextFieldCell
对象来显示一个文本框,用户可以在其中输入或者编辑文本,再或者是NSCell其它别的类型?NSMatrix
对象必须能接纳任何类型的单元,即便是还没创造出来的。这个问题的其中一种解决方案是定义一个
NSMatrix
抽象类,并且要求所有使用它的去声明一个子类,并实现用来创建新单元的方法。因为需要驱实现方法,所以用户可以确信它们创建的对象是正确的类型。但是这个方案需要
NSMatrix
类的用户,这会给扩展带来诸多不便,并且很多开发处于不同项目中,可能会出现重复的开发工作,最后事情变的一团糟。一个更好的方案是,让
NSMatrix
类 适配,允许NSMatrix
实例被某种NSCell
实例化,也就是说,用一个类对象。
NSMatrix
类也可以定义一个setCellClass:
方法,它可以为NSCell
对象传递类对象,而NSMatrix
对象将用来填充空的位置。[myMatrix setCellClass:[NSButtonCell class]];
NSMatrix
对象使用类对象类产生新的单元,当它们被首次初始化并且它的容量需要扩大时。这种定制化在下述情况中可能会有不同,就是当类不是那种可以在消息中传送的并赋给一个变量的对象时。变量和类对象
但你定义了一个新类时,你可以指定一些实例变量。每个类的实例都能维护属于它自己的一份变量副本---每个对象都控制它自己的数据。然而,没有对应于实例变量的类变量。只有初始化自类的定义并未类提供的内部数据结构。再有就是,一个类对象是不能访问任何实例的实例变量的,类变量不能初始化,读取或者改变它们。
对于所有的类的实例来说,要分享数据,你就必须定义一个外部的某种变量。最简单的方法就是在类的实现文件中声明一个变量。
int MCLSGlobalVariable;
@implementation MyClass
// implementation continues
在更好的视线中,你可以将一个变量声明为静态的,并提供类方法来管理它。变量声明成静态会把它的有效域限制在该类,而且仅仅是文件中实现它的类的那部分。(因此不像实例变量,静态变量不能被继承或者被子类直接操作) 这种模式通常被用于定义可分享的类的实例。(例如单例,详情请参考Cocoa Fundamentals Guide 的 “Creating a Singleton Instance” 部分 ).
static MyClass *MCLSSharedInstance;
@implementation MyClass
+ (MyClass *)sharedInstance
{
// check for existence of shared instance
// create if necessary
return MCLSSharedInstance;
}
// implementation continues
静态变量为类对象提供了更多的功能,而非仅仅是一个生产实例的工厂。它能以自己的方式成为一个完整,万能的对象。类对象可以用于调整它创建的实例,从已经创建的对象列表中分配实例,或者管理其它重要的应用进程。当你只需要特定类的一个对象时,你可以把所有的对象的状态放入静态变量中,并只使用类方法。这会为你省去分配,初始化实例的步骤。
注意: 你也可以使用没有声明为静态的外部变量,但是静态变量的有效域限制可以很好的为将数据封装到不同的对象这个目的服务。
初始化一个类变量
如果你想使用一个类变量,除了要分配实例外,你可能还需要把它初始化。尽管程序不会分配类变量,但Objective-C 确实为我们提供一个让程序可以初始化它们的方法。
如果一个类使用了静态或者全局变量,
initialize
是个好方法来设置它们的初始值。例如:如果一个类维护了一个实例数组,initialize
方法可以设置这个数组,升值为其分配一个或者两个默认实例以备使用。在类的父类接收到 initialize 后,并在该类接收到任何其它消息之前,运行时系统会给每个类对象发送一个initialize方法。这样就给类一些时间让它们可以在被使用前有机会对其运行环境进行设置。如果没有初始化的要求,你就不必写一个
initialize
方法来对消息进行响应。由于继承,发送给一个没有实现
initialize
方法的类的initialize
消息将转发给父类,即使父类已经接收到过initialize
方法。例如,假设类A实现了initialize
方法,类B继承了类A,但类B并没有实现initialize
方法。在类B接收到它的第一条消息前,运行时系统会发送一个initialize
方法给它。但是,由于类B并没有实现initialize
,所以将取而代之由类A的initialize
方法来执行。因此,类A需要确保它的初始化逻辑只执行依此,并为恰当的类执行。为了避免多次执行初始化逻辑,在实现
initialize
方法时可以使用下面的模板1-1Listing 1-1
initialize
方法的实现+ (void)initialize
{
if (self == [ThisClass class]) {
// Perform initialization here.
...
}
}
注意: 要记住运行时系统会给每个类单独发送
initialize
方法。因此,你不能给它的父类发送initialize
消息。根类的方法
所有的对象,类以及实例等等,需要有一个面向运行时的借口。类对象跟实例应当能够对它们的能力进行自省,并报告它们在继承树中的位置。而提供这种借口正是
NSObject
类的职责所在。所以说
NSObject
方法没必要被实现两次,一次用来为实例提供运行时借口,又一次为类对象而重复那个接口。类对象被特许执行定义在根类中的实例方法。当一个类对象收到一个它无法以它的类方法进行应答的消息时,运行时系统会决定是否有一个根实例方法能够进行应答。类对象只能在没有类对象可以执行某项任务,才能也仅能执行定义在根类中的实例方法。如果向了解更多有关类对象执行根类实例方法的独特能力,可以参考NSObject Class Reference.
源代码中的类名
在源代码中,类名可以用于两个不同的上下文环境。这些上下文环境反映出类的双重身份:数据类型以及对象。
-
类名可以用于作为某种对象的类型名,例如:
Rectangle *anObject;
这里
anObject
静态的赋予了类型,作为一个指向Rectangle
对象的指针. 编译器会认为它拥有Rectangle
实例的数据结构,定义及继承自Rectangle
类的实例方法。 静态赋予类型使编译器可以做更好的类型检查并使源代码自身更加一目了然。更多信息可以参考 “Enabling Static Behavior” 。只有实例才可以被静态赋予类型,类对象是不可以的,因为它们不是一个类的成员,但属于
Class
数据类型. -
作为消息表达式的接收器,类名代表着类对象。这个用法在之前的例子中都有提到。类名仅在作为消息接收器时可以代表类对象。在其它的任何上下文环境中,你必须请求类对象来获取它的
id
(通过给它发送一个class
消息). 下面的例子把Rectangle
类作为参数放在了一个isKindOfClass:
消息中:if ( [anObject isKindOfClass:[Rectangle class]] )
...
直接使用简单的使用Rectangle这个名字作为参数是非法的。类名只能作为接收器。
如果你在编译时不知道类名,而是在运行时作为一个字符串获取到,那么你可以使用
NSClassFromString
来返回类对象。
NSString *className;
...
if ( [anObject isKindOfClass:NSClassFromString(className)] )
...
如果它传的字符串不是有效的类名的话这个方法将返回nil。
类名存在跟全局变量以及函数名存在于相同的命名空间。一个类跟全局变量不能重名。在Objective-C中类名是唯一具有全局可见的名字。
测试类是否相同
你可以直接使用指针来比较,测试两个类对象,看一下它们是否相同。很重要的一点是,要获取正确的类。在Cocoa框架中有几种特性,动态,透明的子类扩展了已经存在的类以扩展它们的功能。(例如KVO机制,以及Core Data框架就是这样的,可以参考Key-Value Observing Programming Guide 跟 Core Data Programming Guide )在一个动态创建的子类中,类方法通常会被重写,所以子类会装饰成被它取代的类。在测试两个类是否相等时,你需要比较类方法返回的值而非那些低级方法返回的值。按照API的条款,下面的不等式适用于动态子类。
[object class] != object_getClass(object) != *((Class*)object)
因此你应该用下面的方法测试两个类是否相等
if ([objectA class] == [objectB class]) { //...
原文: -
Objects, Classes, and Messaging
This chapter describes the fundamentals of objects, classes, and messaging as used and implemented by the Objective-C language. It also introduces the Objective-C runtime.
The Runtime System
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it dynamically performs operations such as creating objects and determining what method to invoke. Therefore, the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work. Typically, however, you don’t need to interact with the runtime directly. To understand more about the functionality it offers, though, see Objective-C Runtime Programming Guide.
Objects
As the name implies, object-oriented programs are built around objects. An object associates data with the particular operations that can use or affect that data. Objective-C provides a data type to identify an object variable without specifying a particular class of the object.
Object Basics
An object associates data with the particular operations that can use or affect that data. In Objective-C, these operations are known as the object’s methods; the data they affect are its instance variables (in other environments they may be referred to as ivars or member variables). In essence, an object bundles a data structure (instance variables) and a group of procedures (methods) into a self-contained programming unit.
In Objective-C, an object’s instance variables are internal to the object; generally, you get access to an object’s state only through the object’s methods (you can specify whether subclasses or other objects can access instance variables directly by using scope directives, see “The Scope of Instance Variables”). For others to find out something about an object, there has to be a method to supply the information. For example, a rectangle would have methods that reveal its size and position.
Moreover, an object sees only the methods that were designed for it; it can’t mistakenly perform methods intended for other types of objects. Just as a C function protects its local variables, hiding them from the rest of the program, an object hides both its instance variables and its method implementations.
id
In Objective-C, object identifiers are of a distinct data type: id
. This type is the general type for any kind of object regardless of class and can be used for instances of a class and for class objects themselves.
id anObject; |
For the object-oriented constructs of Objective-C, such as method return values, id
replaces int
as the default data type. (For strictly C constructs, such as function return values, int
remains the default type.)
The keyword nil
is defined as a null object, an id
with a value of 0
. id
, nil
, and the other basic types of Objective-C are defined in the header file objc/objc.h
.
id
is defined as pointer to an object data structure:
typedef struct objc_object { |
Class isa; |
} *id; |
Every object thus has an isa
variable that tells it of what class it is an instance. Since the Class
type is itself defined as a pointer:
typedef struct objc_class *Class; |
the isa
variable is frequently referred to as the “isa
pointer.”
Dynamic Typing
The id
type is completely nonrestrictive. By itself, it yields no information about an object, except that it is an object. At some point, a program typically needs to find more specific information about the objects it contains. Since the id
type designator can’t supply this specific information to the compiler, each object has to be able to supply it at runtime.
The isa
instance variable identifies the object’s class—what kind of object it is. Objects with the same behavior (methods) and the same kinds of data (instance variables) are members of the same class.
Objects are thus dynamically typed at runtime. Whenever it needs to, the runtime system can find the exact class that an object belongs to, just by asking the object. (To learn more about the runtime, see Objective-C Runtime Programming Guide.) Dynamic typing in Objective-C serves as the foundation for dynamic binding, discussed later.
The isa
variable also enables objects to perform introspection—to find out about themselves (or other objects). The compiler records information about class definitions in data structures for the runtime system to use. The functions of the runtime system use isa
to find this information at runtime. Using the runtime system, you can, for example, determine whether an object implements a particular method or discover the name of its superclass.
Object classes are discussed in more detail under “Classes.”
It’s also possible to give the compiler information about the class of an object by statically typing it in source code using the class name. Classes are particular kinds of objects, and the class name can serve as a type name. See“Class Types” and “Enabling Static Behavior.”
Memory Management
In any program, it is important to ensure that objects are deallocated when they are no longer needed—otherwise your application’s memory footprint becomes larger than necessary. It is also important to ensure that you do not deallocate objects while they’re still being used.
Objective-C offers three mechanisms for memory management that allow you to meet these goals:
-
Automatic Reference Counting (ARC), where the compiler reasons about the lifetimes of objects.
-
Manual Reference Counting (MRC, sometimes referred to as MRR for “manual retain/release”), where you are ultimately responsible for determining the lifetime of objects.
Manual reference counting is described in Advanced Memory Management Programming Guide.
-
Garbage collection, where you pass responsibility for determining the lifetime of objects to an automatic “collector.”
Garbage collection is described in Garbage Collection Programming Guide. (Not available for iOS—you cannot access this document through the iOS Dev Center.)
Object Messaging
This section explains the syntax of sending messages, including how you can nest message expressions. It also discusses the scope or “visibility” of an object’s instance variables, and the concepts of polymorphism and dynamic binding.
Message Syntax
To get an object to do something, you send it a message telling it to apply a method. In Objective-C, message expressions are enclosed in brackets:
[receiver message] |
The receiver is an object, and the message tells it what to do. In source code, the message is simply the name of a method and any parameters that are passed to it. When a message is sent, the runtime system selects the appropriate method from the receiver’s repertoire and invokes it.
For example, this message tells the myRectangle
object to perform its display
method, which causes the rectangle to display itself:
[myRectangle display]; |
The message is followed by a “;
” as is normal for any statement in C.
Because the method name in a message serves to “select” a method implementation, method names in messages are often referred to as selectors.
Methods can also take parameters, sometimes called arguments. A message with a single parameter affixes a colon (:
) to the name and puts the parameter right after the colon:
[myRectangle setWidth:20.0]; |
For methods with multiple parameters, Objective-C's method names are interleaved with the parameters such that the method’s name naturally describes the parameters expected by the method. The imaginary message below tells the myRectangle
object to set its origin to the coordinates (30.0, 50.0):
[myRectangle setOriginX: 30.0 y: 50.0]; // This is a good example of |
// multiple parameters |
A selector name includes all the parts of the name, including the colons, so the selector in the preceding example is named setOriginX:y:
. It has two colons, because it takes two parameters. The selector name does not, however, include anything else, such as return type or parameter types.
Important The subparts of an Objective-C selector name are not optional, nor can their order be varied. In some languages, the terms “named parameters” and “keyword parameters” carry the implications that the parameters can vary at runtime, can have default values, can be in a different order, and can possibly have additional named parameters. None of these characteristics about parameters are true for Objective-C.
For all intents and purposes, an Objective-C method declaration is simply a C function that prepends two additional parameters (see “Messaging” in the Objective-C Runtime Programming Guide). Thus, the structure of an Objective-C method declaration differs from the structure of a method that uses named or keyword parameters in a language like Python, as the following Python example illustrates:
def func(a, b, NeatMode=SuperNeat, Thing=DefaultThing): |
pass |
Thing
and
NeatMode
might be omitted or might have different values when called.
In principle, a Rectangle
class could instead implement a setOrigin::
method with no label for the second parameter, which would be invoked as follows:
[myRectangle setOrigin:30.0 :50.0]; // This is a bad example of multiple parameters |
While syntactically legal, setOrigin::
does not interleave the method name with the parameters. Thus, the second parameter is effectively unlabeled and it is difficult for a reader of this code to determine the kind or purpose of the method’s parameters.
Methods that take a variable number of parameters are also possible, though they’re somewhat rare. Extra parameters are separated by commas after the end of the method name. (Unlike colons, the commas are not considered part of the name.) In the following example, the imaginary makeGroup:
method is passed one required parameter (group
) and three parameters that are optional:
[receiver makeGroup:group, memberOne, memberTwo, memberThree]; |
Like standard C functions, methods can return values. The following example sets the variable isFilled
to YES
ifmyRectangle
is drawn as a solid rectangle, or NO
if it’s drawn in outline form only.
BOOL isFilled; |
isFilled = [myRectangle isFilled]; |
Note that a variable and a method can have the same name.
One message expression can be nested inside another. Here, the color of one rectangle is set to the color of another:
[myRectangle setPrimaryColor:[otherRect primaryColor]]; |
Objective-C also provides a dot (.
) operator that offers a compact and convenient syntax for invoking an object’s accessor methods. The dot operator is often used in conjunction with the declared properties feature (see“Declared Properties”) and is described in “Dot Syntax.”
Sending Messages to nil
In Objective-C, it is valid to send a message to nil
—it simply has no effect at runtime. There are several patterns in Cocoa that take advantage of this fact. The value returned from a message to nil
may also be valid:
-
If the method returns an object, then a message sent to
nil
returns0
(nil
). For example:Person *motherInLaw = [[aPerson spouse] mother];
If the
spouse
object here isnil
, thenmother
is sent tonil
and the method returnsnil
. -
If the method returns any pointer type, any integer scalar of size less than or equal to
sizeof(void*)
, afloat
, adouble
, along double
, or along long
, then a message sent tonil
returns0
. -
If the method returns a
struct
, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, then a message sent tonil
returns0.0
for every field in thestruct
. Otherstruct
data types will not be filled with zeros. -
If the method returns anything other than the aforementioned value types, the return value of a message sent to
nil
is undefined.
The following code fragment illustrates a valid use of sending a message to nil
.
id anObjectMaybeNil = nil; |
|
// this is valid |
if ([anObjectMaybeNil methodThatReturnsADouble] == 0.0) |
{ |
// implementation continues... |
} |
The Receiver’s Instance Variables
A method has automatic access to the receiving object’s instance variables. You don’t need to pass them to the method as parameters. For example, the primaryColor
method illustrated above takes no parameters, yet it can find the primary color for otherRect
and return it. 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. TheprimaryColor
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 a Rectangle
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 invokeaccessor 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"]; |
Classes
An object-oriented program is typically built from a variety of objects. A program based on the Cocoa frameworks might use NSMatrix
objects, NSWindow
objects, NSDictionary
objects, NSFont
objects, NSText
objects, and many others. Programs often use more than one object of the same kind or class—several NSArray
objects orNSWindow
objects, for example.
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 asuperclass 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 a drawing program.
![](https://i-blog.csdnimg.cn/blog_migrate/ea1630818863538344274da73992f51d.gif)
Figure 1-1 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 aSquare
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 an object of type Square
isn’t only a square, it’s also a rectangle, a shape, a graphic, and an object of type 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 and incorporate them 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.
Note: Implementing a new root class is a delicate task and one with many hidden hazards. The class must duplicate much of what the NSObject
class does, such as allocate instances, connect them to their class, and identify them to the runtime system. For this reason, you should generally use the NSObject
class provided with Cocoa as the root class. For more information, see NSObject Class Reference and the NSObject Protocol Reference.
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 aRectangle
class 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.
![](https://i-blog.csdnimg.cn/blog_migrate/5e1d53ba0a45e1a5b323d6915e26d307.gif)
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.
Overriding One Method with Another
There’s one useful exception to inheritance: When you define a new class, you can implement a new method with the same name as one defined in a class farther up the hierarchy. The new method overrides the original; instances of the new class perform it rather than the original, and subclasses of the new class inherit it rather than the original.
For example, Graphic
defines a display
method that Rectangle
overrides by defining its own version ofdisplay
. The Graphic
method is available to all kinds of objects that inherit from the Graphic
class—but not toRectangle
objects, which instead perform the Rectangle
version of display
.
Although overriding a method blocks the original version from being inherited, other methods defined in the new class can skip over the redefined method and find the original (see “Messages to self and super” to learn how).
A redefined method can also incorporate the very method it overrides. When it does, the new method serves only to refine or modify the method it overrides, rather than replace it outright. When several classes in the hierarchy define the same method, but each new version incorporates the version it overrides, the implementation of the method is effectively spread over all the classes.
Although a subclass can override inherited methods, it can’t override inherited instance variables. Because an object has memory allocated for every instance variable it inherits, you can’t override an inherited variable by declaring a new one with the same name. If you try, the compiler will complain.
Abstract Classes
Some classes are designed only or primarily so that other classes can inherit from them. These abstract classesgroup methods and instance variables that can be used by a number of subclasses into a common definition. The abstract class is typically incomplete by itself, but contains useful code that reduces the implementation burden of its subclasses. (Because abstract classes must have subclasses to be useful, they’re sometimes also calledabstract superclasses.)
Unlike some other languages, Objective-C does not have syntax to mark classes as abstract, nor does it prevent you from creating an instance of an abstract class.
The NSObject
class is the canonical example of an abstract class in Cocoa. You never use instances of theNSObject
class in an application—it wouldn’t be good for anything; it would be a generic object with the ability to do nothing in particular.
The NSView
class, on the other hand, provides an example of an abstract class, instances of which you might occasionally use directly.
Abstract classes often contain code that helps define the structure of an application. When you create subclasses of these classes, instances of your new classes fit effortlessly into the application structure and work automatically with other objects.
Class Types
A class definition is a specification for a kind of object. The class, in effect, defines a data type. The type is based not just on the data structure the class defines (instance variables), but also on the behavior included in the definition (methods).
A class name can appear in source code wherever a type specifier is permitted in C—for example, as an argument to the sizeof
operator:
int i = sizeof(Rectangle); |
Static Typing
You can use a class name in place of id
to designate an object’s type:
Rectangle *myRectangle; |
Because this way of declaring an object type gives the compiler information about the kind of object it is, it’s known as static typing. Just as id
is actually a pointer, objects are statically typed as pointers to a class. Objects are always typed by a pointer. Static typing makes the pointer explicit; id
hides it.
Static typing permits the compiler to do some type checking—for example, to warn if an object could receive a message that it appears not to be able to respond to—and to loosen some restrictions that apply to objects generically typed id
. In addition, it can make your intentions clearer to others who read your source code. However, it doesn’t defeat dynamic binding or alter the dynamic determination of a receiver’s class at runtime.
An object can be statically typed to its own class or to any class that it inherits from. For example, because inheritance makes a Rectangle
object a kind of Graphic
object (as shown in the example hierarchy in Figure 1-1), a Rectangle
instance can be statically typed to the Graphic
class:
Graphic *myRectangle; |
Static typing to the superclass is possible here because a Rectangle
object is a Graphic
object. In addition, it’s more than that because it also has the instance variables and method capabilities of Shape
and Rectangle
objects, but it’s a Graphic
object nonetheless. For purposes of type checking, given the declaration described here, the compiler considers myRectangle
to be of type Graphic
. At runtime, however, if the myRectangle
object is allocated and initialized as an instance of Rectangle
, it is treated as one.
See “Enabling Static Behavior” for more on static typing and its benefits.
Type Introspection
Instances can reveal their types at runtime. The isMemberOfClass:
method, defined in the NSObject
class, checks whether the receiver is an instance of a particular class:
if ( [anObject isMemberOfClass:someClass] ) |
... |
The isKindOfClass:
method, also defined in the NSObject
class, checks more generally whether the receiver inherits from or is a member of a particular class (whether it has the class in its inheritance path):
if ( [anObject isKindOfClass:someClass] ) |
... |
The set of classes for which isKindOfClass:
returns YES
is the same set to which the receiver can be statically typed.
Introspection isn’t limited to type information. Later sections of this chapter discuss methods that return the class object, report whether an object can respond to a message, and reveal other information.
See NSObject Class Reference for more on isKindOfClass:
, isMemberOfClass:
, and related methods.
Class Objects
A class definition contains various kinds of information, much of it about instances of the class:
-
The name of the class and its superclass
-
A template describing a set of instance variables
-
The declarations of method names and their return and parameter types
-
The method implementations
This information is compiled and recorded in data structures made available to the runtime system. The compiler creates just one object, a class object, to represent the class. The class object has access to all the information about the class, which means mainly information about what instances of the class are like. It’s able to produce new instances according to the plan put forward in the class definition.
Although a class object keeps the prototype of a class instance, it’s not an instance itself. It has no instance variables of its own and it can’t perform methods intended for instances of the class. However, a class definition can include methods intended specifically for the class object—class methods as opposed to instance methods. A class object inherits class methods from the classes above it in the hierarchy, just as instances inherit instance methods.
In source code, the class object is represented by the class name. In the following example, the Rectangle
class returns the class version number using a method inherited from the NSObject
class:
int versionNumber = [Rectangle version]; |
However, the class name stands for the class object only as the receiver in a message expression. Elsewhere, you need to ask an instance or the class to return the class id
. Both respond to a class
message:
id aClass = [anObject class]; |
id rectClass = [Rectangle class]; |
As these examples show, class objects can, like all other objects, be typed id
. But class objects can also be more specifically typed to the Class
data type:
Class aClass = [anObject class]; |
Class rectClass = [Rectangle class]; |
All class objects are of type Class
. Using this type name for a class is equivalent to using the class name to statically type an instance.
Class objects are thus full-fledged objects that can be dynamically typed, receive messages, and inherit methods from other classes. They’re special only in that they’re created by the compiler, lack data structures (instance variables) of their own other than those built from the class definition, and are the agents for producing instances at runtime.
Note: The compiler also builds a metaclass object for each class. It describes the class object just as the class object describes instances of the class. But while you can send messages to instances and to the class object, the metaclass object is used only internally by the runtime system.
Creating Instances
A principal function of a class object is to create new instances. This code tells the Rectangle
class to create a new rectangle instance and assign it to the myRectangle
variable:
id myRectangle; |
myRectangle = [Rectangle alloc]; |
The alloc
method dynamically allocates memory for the new object’s instance variables and initializes them all to0
—all, that is, except the isa
variable that connects the new instance to its class. For an object to be useful, it generally needs to be more completely initialized. That’s the function of an init
method. Initialization typically follows immediately after allocation:
myRectangle = [[Rectangle alloc] init]; |
This line of code, or one like it, would be necessary before myRectangle
could receive any of the messages that were illustrated in previous examples in this chapter. The alloc
method returns a new instance and that instance performs an init
method to set its initial state. Every class object has at least one method (like alloc
) that enables it to produce new objects, and every instance has at least one method (like init
) that prepares it for use. Initialization methods often take parameters to allow particular values to be passed and have keywords to label the parameters (initWithPosition:size:
, for example, is a method that might initialize a new Rectangle
instance), but every initialization method begins with “init
”.
Customization with Class Objects
It’s not just a whim of the Objective-C language that classes are treated as objects. It’s a choice that has intended, and sometimes surprising, benefits for design. It’s possible, for example, to customize an object with a class, where the class belongs to an open-ended set. In AppKit, for example, an NSMatrix
object can be customizedwith a particular kind of NSCell
object.
An NSMatrix
object can take responsibility for creating the individual objects that represent its cells. It can do this when the matrix is first initialized and later when new cells are needed. The visible matrix that an NSMatrix
object draws on the screen can grow and shrink at runtime, perhaps in response to user actions. When it grows, the matrix needs to be able to produce new objects to fill the new slots that are added.
But what kind of objects should they be? Each matrix displays just one kind of NSCell
, but there are many different kinds. The inheritance hierarchy in Figure 1-3 shows some of those provided by AppKit. All inherit from the generic NSCell
class.
![](https://i-blog.csdnimg.cn/blog_migrate/ba23b4d11c5d1a18ca9e77e1ef478dca.gif)
When a matrix creates NSCell
objects, should they be NSButtonCell
objects to display a bank of buttons or switches, NSTextFieldCell
objects to display fields where the user can enter and edit text, or some other kind ofNSCell
? The NSMatrix
object must allow for any kind of cell, even types that haven’t been invented yet.
One solution to this problem would be to define the NSMatrix
class as abstract and require everyone who uses it to declare a subclass and implement the methods that produce new cells. Because they would be implementing the methods, users could make certain that the objects they created were of the right type.
But this solution would require users of the NSMatrix
class to do work that ought to be done in the NSMatrix
class itself, and it unnecessarily proliferates the number of classes. Because an application might need more than one kind of matrix, each with a different kind of cell, it could become cluttered with NSMatrix
subclasses. Every time you invented a new kind of NSCell
, you’d also have to define a new kind of NSMatrix
. Moreover, programmers on different projects would be writing virtually identical code to do the same job, all to make up for the failure of NSMatrix
to do it.
A better solution, and the solution the NSMatrix
class adopts, is to allow NSMatrix
instances to be initialized with a kind of NSCell
—that is, with a class object. The NSMatrix
class also defines a setCellClass:
method that passes the class object for the kind of NSCell
object an NSMatrix
should use to fill empty slots:
[myMatrix setCellClass:[NSButtonCell class]]; |
The NSMatrix
object uses the class object to produce new cells when it’s first initialized and whenever it’s resized to contain more cells. This kind of customization would be difficult if classes weren’t objects that could be passed in messages and assigned to variables.
Variables and Class Objects
When you define a new class, you can specify instance variables. Every instance of the class can maintain its own copy of the variables you declare—each object controls its own data. There is, however, no class variablecounterpart to an instance variable. Only internal data structures, initialized from the class definition, are provided for the class. Moreover, a class object has no access to the instance variables of any instances; it can’t initialize, read, or alter them.
For all the instances of a class to share data, you must define an external variable of some sort. The simplest way to do this is to declare a variable in the class implementation file:
int MCLSGlobalVariable; |
|
@implementation MyClass |
// implementation continues |
In a more sophisticated implementation, you can declare a variable to be static
, and provide class methods to manage it. Declaring a variable static
limits its scope to just the class—and to just the part of the class that’s implemented in the file. (Thus unlike instance variables, static variables cannot be inherited by, or directly manipulated by, subclasses.) This pattern is commonly used to define shared instances of a class (such as singletons; see “Creating a Singleton Instance” in Cocoa Fundamentals Guide).
static MyClass *MCLSSharedInstance; |
|
@implementation MyClass |
|
+ (MyClass *)sharedInstance |
{ |
// check for existence of shared instance |
// create if necessary |
return MCLSSharedInstance; |
} |
// implementation continues |
Static variables help give the class object more functionality than just that of a factory producing instances; it can approach being a complete and versatile object in its own right. A class object can be used to coordinate the instances it creates, dispense instances from lists of objects already created, or manage other processes essential to the application. In the case when you need only one object of a particular class, you can put all the object’s state into static variables and use only class methods. This saves the step of allocating and initializing an instance.
Note: It is also possible to use external variables that are not declared static
, but the limited scope of static variables better serves the purpose of encapsulating data into separate objects.
Initializing a Class Object
If you want to use a class object for anything besides allocating instances, you may need to initialize it just as you would an instance. Although programs don’t allocate class objects, Objective-C does provide a way for programs to initialize them.
If a class makes use of static or global variables, the initialize
method is a good place to set their initial values. For example, if a class maintains an array of instances, the initialize
method could set up the array and even allocate one or two default instances to have them ready.
The runtime system sends an initialize
message to every class object before the class receives any other messages and after its superclass has received the initialize
message. This sequence gives the class a chance to set up its runtime environment before it’s used. If no initialization is required, you don’t need to write aninitialize
method to respond to the message.
Because of inheritance, an initialize
message sent to a class that doesn’t implement the initialize
method is forwarded to the superclass, even though the superclass has already received the initialize
message. For example, assume class A implements the initialize
method, and class B inherits from class A but does not implement the initialize
method. Just before class B is to receive its first message, the runtime system sendsinitialize
to it. But, because class B doesn’t implement initialize
, class A’s initialize
is executed instead. Therefore, class A should ensure that its initialization logic is performed only once, and for the appropriate class.
To avoid performing initialization logic more than once, use the template in Listing 1-1 when implementing theinitialize
method.
Listing 1-1 Implementation of the initialize method
+ (void)initialize |
{ |
if (self == [ThisClass class]) { |
// Perform initialization here. |
... |
} |
} |
Note: Remember that the runtime system sends initialize
to each class individually. Therefore, in a class’s implementation of the initialize
method, you must not send the initialize
message to its superclass.
Methods of the Root Class
All objects, classes and instances alike, need an interface to the runtime system. Both class objects and instances should be able to introspect about their abilities and to report their place in the inheritance hierarchy. It’s the province of the NSObject
class to provide this interface.
So that NSObject
methods don’t have to be implemented twice—once to provide a runtime interface for instances and again to duplicate that interface for class objects—class objects are given special dispensation to perform instance methods defined in the root class. When a class object receives a message that it can’t respond to with a class method, the runtime system determines whether there’s a root instance method that can respond. The only instance methods that a class object can perform are those defined in the root class, and only if there’s no class method that can do the job.
For more on this peculiar ability of class objects to perform root instance methods, see NSObject Class Reference.
Class Names in Source Code
In source code, class names can be used in only two very different contexts. These contexts reflect the dual role of a class as a data type and as an object:
-
The class name can be used as a type name for a kind of object. For example:
Rectangle *anObject;
Here
anObject
is statically typed to be a pointer to aRectangle
object. The compiler expects it to have the data structure of aRectangle
instance and to have the instance methods defined and inherited by theRectangle
class. Static typing enables the compiler to do better type checking and makes source code more self-documenting. See “Enabling Static Behavior” for details.Only instances can be statically typed; class objects can’t be, because they aren’t members of a class, but rather belong to the
Class
data type. -
As the receiver in a message expression, the class name refers to the class object. This usage was illustrated in several of the earlier examples. The class name can stand for the class object only as a message receiver. In any other context, you must ask the class object to reveal its
id
(by sending it aclass
message). This example passes theRectangle
class as a parameter in anisKindOfClass:
message:if ( [anObject isKindOfClass:[Rectangle class]] )
...
It would have been illegal to simply use the name “Rectangle” as the parameter. The class name can only be a receiver.
If you don’t know the class name at compile time but have it as a string at runtime, you can use
NSClassFromString
to return the class object:NSString *className;
...
if ( [anObject isKindOfClass:NSClassFromString(className)] )
...
This function returns
nil
if the string it’s passed is not a valid class name.
Class names exist in the same namespace as global variables and function names. A class and a global variable can’t have the same name. Class names are the only names with global visibility in Objective-C.
Testing Class Equality
You can test two class objects for equality using a direct pointer comparison. It is important, though, to get the correct class. There are several features in the Cocoa frameworks that dynamically and transparently subclass existing classes to extend their functionality (for example, key-value observing and Core Data do this—see Key-Value Observing Programming Guide and Core Data Programming Guide respectively). In a dynamically-created subclass, the class
method is typically overridden such that the subclass masquerades as the class it replaces. When testing for class equality, you should therefore compare the values returned by the class
method rather than those returned by lower-level functions. Put in terms of API, the following inequalities pertain for dynamic subclasses:
[object class] != object_getClass(object) != *((Class*)object) |
You should therefore test two classes for equality as follows:
if ([objectA class] == [objectB class]) { //... |