Objective-C 编程语言官网文档(三)-如何定义类
2012-05-30 18:14
549 查看
声明:本文档仅为个人学习过程中顺手翻译之作,方便开发的同胞借鉴参考。如有觉得译的不好不到位的地方,欢迎指正,将及时做出更正
尽量尊重原文档,因为首次Objective-C,有些地方可能直译了没有注意该语言的专有词,希望指正。如需转载,请注明出处
我的编程环境:
IDE:XCODE4.3.1
OS:MACOSX10.7.4
文章来译自:http://developer.apple.com/
如何定义类
大多数面对对象编程都包含为新对象编写代码---定义一个新类。在Objective-C中,类被定义为两部分:一个接口声明有一些方法以及类的属性,以及它的父类的名字
一个实现类真正的类(包括实现的方法的代码)
下面的几部分通常在它自己的文件里。然而,有是有,一个类的定义可能通过类别的使用跨越几个文件。类目跨越划分类的定义或者扩展一个已经存在的。类别的描述跨越参考“Categories
andExtensions.”
源文件
通常类的接口跟实现是在两个不同的文件中,即便编译器没有强制要求。接口文件必须对任何想要使用该类的人可用。一个文件可以声明或者实现不止一个类。不过按照惯例,通常每个类都有一个单独的接口文件,以及一个单独的实现文件。保持类接口的独立能够更好的反映它们作为独立实体的状态。
接口和实现文件都用该类命名。实现类文件的名字的扩展名是
.m,指明它含有Objective-C源码.接口文件可以使用任何别的扩展。因为它是被包含在源文件中的,接口文件通常使用典型的头文件所使用的
.h扩展名。例如,
Rectangle类可以定义在
Rectangle.h以及
Rectangle.m中。
让一个对象的接口与实现独立开来的做法很好的符合了面向对象设计的思想。一个对象是一种自包含的实体,它可以从外部被当做类似黑盒一样访问。一旦你决定你的对象如何与你程序中的其它元素进行交互,也就是说,一旦你声明好它的接口,你就可以自由的修改它的实现,而不会影响到应用的其它部分了。
类接口
类接口的声明是以编译指令@interface开头并以指令
@end结尾。(所有的Objective-C
编译器指令都是以“@”开头.)
@interfaceClassName:ItsSuperclass |
//Methodandpropertydeclarations. |
@end |
类的方法以及属性定义在之后,类声明结尾处的前面。能被类对象使用的方法名,即类方法,方法前面会有个加号标记;
+alloc; |
-(void)display; |
Circle有一个
radius方法与
radius实例变量相匹配。
方法的返回值类型的名声使用的是标准的C语法:
-(float)radius; |
-(void)setRadius:(float)aRadius; |
alloc方法会返回
id.
当有更多的参数时,所有的参数会声明在方法名内,在冒号的后面。例如:
-(void)setWidth:(float)widthheight:(float)height; |
-makeGroup:group,...; |
@property(attributes)TypepropertyName; |
Properties.”
注意:过去,接口还要求类的实例变量的声明,数据构造器是每个类实例的一部分。它们定义在
@interface声明后面的大括号离,方法声明之前。
@interfaceClassName:ItsSuperclass |
{ |
//Instancevariabledeclarations. |
} |
//Methodandpropertydeclarations. |
@end |
导入接口
接口文件必须被包含在任何依赖这个类接口的源码模块中,即任何需要为这个类创建实例的模块,发送一个消息来调用为这个类声明的方法,或者使用定义在类中的实例变量。接口通常包含在#import
指令中
#import"Rectangle.h" |
#include的作用是一样的,唯一的区别是这条指令可以确保同一个文件不会被导入多次。因此在Objective-C的基础文档中的例子大多使用这个指令。
为了反映一个类的定义是建立在所继承的类的定义基础上,那么这个接口文件就以导入父类的接口开始。
#import"ItsSuperclass.h" |
@interfaceClassName:ItsSuperclass |
//Methodandpropertydeclarations. |
@end |
如果有一个precomp—一个预编译头文件—支持父类,你可能会更愿意导入预编译的文件。
引用其它类
当一个接口文件声明了一个类,导入了它的父类,显示包含了所有继承到的类的声明,从NSObject开始一直到它的父类。如果接口引用了不在它继承树中的类,它就必须显示的导入它们或者用
@class
指令声明它们
@classRectangle,Circle; |
在接口文件中,当它静态的给实例变量,返回值以及参数赋予类型时,用到了类名
-(void)setPrimaryColor:(NSColor*)aColor; |
NSColor类.
因为向这样声明只是简单将类名用作赋予类型,并不依赖任何接口的详情(它的方法和实例变量),
@class指令已经可以为编译器提供足够的有关编译器能够获得什么信息预警。但是,当类的接口真正使用时(实例创建时,发送消息时),
此时类接口就必须被导入。通常接口文件会使用
@class指令来生命类,而真正的响应的实现文件会导入它们的接口(因为它需要为这些类创建实例并给它们发消息)。
@class指令将给编译器以及链接器看的代码量最小化
,同时也是最简单的方式来提供类名的前置声明。让事情变的简单,它可以有效避免导入的文件本身还导入了别的文件时可能出现的潜在问题。例如,当一个类声明了两外一个类的一个静态类型的实例变量,并且他们的两个接口文件相互导入,这种情况下可能两个类都无法正确编译。
接口的职责
接口文件的职责是为其它源代码模块(以及其它程序猿)声明新类。包含它们的类协同工作需要的一些信息(程序猿可能比较喜欢还有点文档).接口文件告诉用户类是怎么接入继承树的,以及需要用到的其它的类,继承的或者在类中某个位置引用到的。
通过它的方法声明列表,接口文件让其它模块知道什么样的消息可以发送给类对象以及类的实例。每个能在类声明外部使用的方法都声明在接口文件中。实现类内部使用的方法可以忽略
类的实现
T类的定义就像它的声明一样,都是结构化的。以@implementation指令开始,
@end指令结束.
另外,类还可能会在
@implementation指令后面的大括号中声明实例变量:
@implementationClassName |
{ |
//Instancevariabledeclarations. |
} |
//Methoddefinitions. |
@end |
Properties”).如果你没有额外的实例变量要声明,那么你可以忽略上面提到的大括号。
@implementationClassName |
//Methoddefinitions. |
@end |
Rectangle.m类要导入
Rectangle.h.因为实现不需要重复任何它导入的声明,它可以安全的忽略父类的名字。
类的方法的定义,就像C函数一样,有一堆大括号。在大括号前面,它们的声明跟接口文件中的习惯一样,但没有分号。例如:
+(id)alloc{ |
... |
} |
-(BOOL)isFilled{ |
... |
} |
-(void)setFilled:(BOOL)flag{ |
... |
} |
#import<stdarg.h> |
... |
-getGroup:group,...{ |
va_listap; |
va_start(ap,group); |
... |
} |
引用实例变量
默认情况下,实例方法的定义会拥有这个对象所在域的所有实例变量。它可以简单的通过名字来引用它们。尽管编译器创建了完全等价于C的构造器来存储实例变量,但构造器的具体形态被隐藏了。你不需要任何一个构造运算符(.或者->)来引用对象的数据。例如这个方法定义引用了receiver的filled实例变量。
-(void)setFilled:(BOOL)flag |
{ |
filled=flag; |
... |
} |
filled实例变量都没有被声明成这个方法的一个参数,然而这个实例变量还是在它的作用域内。这种方法语法的简化在Objective-C代码书写中是一种有效的速记方式.
当实例变量属于的对象不是receiver的,对象的类型必须通过静态赋予类型显式的呈献给编译器。在引用一个赋予静态类型的对象的实例变量时,将使用构造指针运算符(
->)。
假设,
Sibling类声明了一个赋予静态类型的对象,
twin,作为实例变量:
@interfaceSibling:NSObject |
{ |
Sibling*twin; |
intgender; |
structfeatures*appearance; |
} |
Sibling方法就可以直接设置它们:
-makeIdenticalTwin |
{ |
if(!twin){ |
twin=[[Siblingalloc]init]; |
twin->gender=gender; |
twin->appearance=appearance; |
} |
returntwin; |
} |
实例变量的作用域
如果向要对象隐藏它的数据,编译器可以限制实例变量的作用域—即,限制它们在程序中的可见度。但为了提供良好的可伸缩性,它同时允许你显式的把作用域设置为4个等级,每个等级都有对应的编译器指令。指令 | 含义 |
---|---|
@private | 实例变量仅在声明它的类中可被访问。 |
@protected | 实例变量在声明它的类中以及继承它的子类中可被访问。如果一个实例变量没有任何显式的声明指令, 那么默认就是 @protected作用域. |
@public | 实例变量随处都可以被访问 |
@package | 使用modern运行时,(modern跟legacyruntime相关介绍可以参考@package实例变量在可执行的实现类镜像中作用域为 @public,但对于外部来说则是 @private. 对于Objective-C作用域与C 语言中的 private_extern对其变量和函数的作用类似。任何外部实现类的镜像中的代码试图使用这种实例变量时,会得到一个链接错误。 这种作用域在框架类中的实例变量很有用,在这种地方 @private可能太过严苛,而 @protected跟 @public又授权过度。 |
图2-1实例变量的作用域(
@package作用域没有展示)
一个作用域指令可以作用到它后面的所有实例变量列表,直到下一个指令出现,或者列表结束。下面的例子中,
age和
evaluation实例变量是私有的;
name,
job,
和
wage是protected;
boss是公开的.
@interfaceWorker:NSObject |
{ |
char*name; |
@private |
intage; |
char*evaluation; |
@protected |
idjob; |
floatwage; |
@public |
idboss; |
} |
name,
默认为
@protected.
类声明的所有实例变量,无论它们的标识是什么,都在类定义的作用域之内。例如,一个类声明了一个
job实例变量,例如上面的
Worker类,可以在一个方法定义中引用它。
-promoteTo:newPosition |
{ |
idold=job; |
job=newPosition; |
returnold; |
} |
通常,一个类也会访问它继承的实例变量。这种引用实例变量的能力通常与变量以其被继承。类拥有它们作用域内的完整数据结构,这点很有意义,尤其是当你想要一个类的定义像继承来的类一样精巧时。早前提到过的
promoteTo:方法跟定义在任何类中一样,从
Worker类中集成到了
job实例变量。
但是,有时候你可能想要严格限制子类直接获取实例变量:
一旦子类访问了一个实例变量,那么声明这个变量的类就跟它的实现绑定到一起。在以后的版本中,就不能清除这个变量或者改变它的职能而不会无意中破坏了子类。
此外,当子类访问一个继承的实例变量并修改了它的值,它可能在声明这个变量的类中引入一个bug,尤其是在构造器中引用变量时.
要将实例变量作用域限制在声明它的类中,你就应该使用
@private.子类要想访问父类中
@private声明的实例变量,就必须通过调用声明在父类中的公开的方法(该方法中提供了访问私有方法的途径),当然前提是这种共有方法存在。
另一种极端是把变量标识为
@public,让它随处都可被访问。通常,要想获得的存储在实例变量中的信息,其它对象必须给它发消息请求。但公有实例变量可以随处访问。就像C结构中的字段一样。例如
Worker*ceo=[[Workeralloc]init]; |
ceo->boss=nil; |
将实例变量标识为
@public破坏了对象隐藏自身数据的能力。这与面向对象编程的基本思想背道而驰—使用对象对数据进行封装,可以有效避免查看跟一些无意的错误。应当尽量避免公有实例变量除非确实必要。
给
self跟
super发消息
Objective-C提供了两个元素,可以用在方法定义中来引用执行这个方法的对象—
self跟
super.(有点绕,呵呵,看的明白不)
例如,你定义了一个
reposition方法,它需要在object做任何动作时改变坐标。它可以调用
setOrigin::方法来改变坐标,你需要做的是发送一个
setOrigin::给消息给一个对象,这个对象就是
reposition消息自身的发送对象。当你在写
reposition代码时,你可以通过
self或者
super
来引用刚提到的对象。
-reposition |
{ |
... |
[selfsetOrigin:someX:someY]; |
... |
} |
-reposition |
{ |
... |
[supersetOrigin:someX:someY]; |
... |
} |
self跟
super都引用的是接收
reposition消息的对象。然而这两个东东还是有很大区别的.
self是消息例程传送给每个方法的一个隐藏参数。它是一个本地变量,并可以在一个方法实现中自由的使用。
super只有作为消息表达式的***时才能替代
self。作为***,这两个东东的主要区别在于它们是如何影响消息进程的:
self按照常规搜索方法实现,开始于接收对象的类的派发表中。在上面的例子中,开始于接收reposition消息的对象的类.
super是一个标识,它告诉编译器去到一个完全不同的地方去查找这个方法实现。它开始于定义了含有
super的方法的类的父类中。(真心绕啊
).
在上面的例子中,它开始于定义reposition的类的父类中.
只要
super收到一条消息,编译器就会用另外一个消息例程来替代
objc_msgSend函数.替代例程直接查找定义类的父类,即发消息给
super
的类的父类—而不是接收消息的对象的类。
看一个使用self跟super的例子:
当我们使用有继承关系的3个类时,self跟
super的区别将变的很清楚。假如,我们创建一个属于
Low类的对象,
Low类的父类是
Mid;
Mid的父类是
High.
三个类都定义了一个方法叫做
negotiate,每个类都处于自己的考虑去使用这个方法。另外,
Mid类定义了一个NX的方法叫做
makeLastingPeace,
这个方法用到了
negotiate方法.上面提到的方法和类可以在图2-2中看到
图2-2High,Mid,andLow的继承关系
假设在
makeLastingPeace方法实现中(
Mid类中)使用
self来只带发送
negotiate消息的对象:
-makeLastingPeace |
{ |
[selfnegotiate]; |
... |
} |
Low对象以执行
makeLastingPeace方法,
makeLastingPeace会发哦那个一条
negotiate消息给相同的
Low对象.
消息例程找到了定义在Low中的
negotiate
版本,
self
的类.
但是,如果
makeLastingPeace的实现使用
super来作为***,
-makeLastingPeace |
{ |
[supernegotiate]; |
... |
} |
High中定义的
negotiate版本.
而将接收
makeLastingPeace消息的对象的类(
Low)
忽略掉并直接跳到父类
Mid中,因为makeLastingPeace是定义在
Mid中的.
实现也没有找到
Mid版本的
negotiate.
在这个例子中,
super提供了一种绕过某个方法的途径。这里,我们使用
super以使
makeLastingPeace绕过
Mid中覆写了父类版本的
negotiate的方法。
无法获取
Mid版本的
negotiate方法,可能看起来像个缺陷,但在某些环境下,它是有意为之的:
Low类的作者故意覆写了
Mid版本的
negotiate方法,所以
Low(以及其子类)
的实例将调用覆写版本的方法。
Low的设计者并不像
Low对象执行继承来的方法。
Mid的方法
makeLastingPeace方法的作者,在发送
negotiate消息给
super(就像在第二个实现中展示的),
故意跳过了
Mid版本的
negotiate(以及任何定义在继承了
Mid类似
的
Low的类)以执行定义在
High类中的版本.第二个
makeLastingPeace实现的设计者希望使用
High中定义的
negotiate版本而不是别的类中的。
Mid版本的
negotiate仍可以使用,但需要发送一个直接的消息给
Mid实例来达到这个目的;
使用super
发送给super的消息允许方法实现被分发给多个类。你可以覆写一个已经存在的方法来增加或者修改一些东西。并在修改中结合原始方法。
-negotiate |
{ |
... |
return[supernegotiate]; |
} |
super完成剩下的.能够初始化新分配的实例
的
init方法,就是设计用来像这样工作的。每个
init方法都有自己初始化定义在类中的实例变量的职责。但在做这些之前,它要发送一个
init消息给
super来获取继承来的类并实例化它们的实例变量。每个版本的
init都遵循这个流程,所以类都是按照继承顺序来初始化它们的实例变量的。
-(id)init |
{ |
self=[superinit]; |
if(self){ |
... |
} |
} |
super并入这个方法。例如,每个创建实例的类方法都必须为新对象分配存储单元并初始化它类构造器的
isa变量.
分配通常留下给定义在
NSObject类中的
alloc
和
allocWithZone:方法来做。如果两外一个类覆写了这些方法(一种少见的情况),
它仍然可以通过发送消息给
super获取基础功能。
重定义self
super只是一个简单的标识,告诉编译器应该从哪里查找要执行的方法。它仅用于消息的***。但
self则是一个变量名,使用的方式很多,甚至可以赋予一个新值。
有一种情况就是定义在类方法中。类方法通常并不关心类对象,而是类的实例。例如,许多类方法集成了一个实例的allocation和initialization,通常同时设置实例变量的值。在这种方法中,可能忍不住会发消息给新分配的实例并调用实例
self,
就像在实例方法中那样。但这样做是错误的。
self和
super都both指向接收的对象—该对象收到一条消息说让他执行这个方法。在一个实例方法内部,
self指向的是实例。但在类方法的内部,
self指向的是类对象。我们不能像下例这样做:
+(Rectangle*)rectangleOfColor:(NSColor*)color |
{ |
self=[[Rectanglealloc]init];//BAD |
[selfsetColor:color]; |
returnself; |
} |
self来指代一个实例:
+(id)rectangleOfColor:(NSColor*)color |
{ |
idnewInstance=[[Rectanglealloc]init];//GOOD |
[newInstancesetColor:color]; |
returnnewInstance; |
} |
alloc消息给类方法,不如发送
alloc给
self.
这种方式下,如果类是子类,并且
rectangleOfColor:消息被子类接收到,这个实例返回的类型会与子类相同(例如,
NSArray[b]的[/b]
array方法被
NSMutableArray所继承[b])[/b]。
+(id)rectangleOfColor:(NSColor*)color |
{ |
idnewInstance=[[selfalloc]init];//EXCELLENT |
[newInstancesetColor:color]; |
returnnewInstance; |
} |
英文原文:
(
DefiningaClass
Muchofobject-orientedprogrammingconsistsofwritingthecodefornewobjects—definingnewclasses.InObjective-C,classesaredefinedintwoparts:Aninterfacethatdeclaresthemethodsandpropertiesoftheclassand
namesitssuperclass
Animplementationthatactuallydefinestheclass(containsthecodethat
implementsitsmethods)
Eachofthesepartsistypicallyinitsownfile.Sometimes,however,aclassdefinitionspansseveralfilesthroughtheuseofafeaturecalledacategory.Categoriescancompartmentalizeaclassdefinitionorextendanexistingone.Categoriesare
describedin“Categories
andExtensions.”
SourceFiles
Althoughthecompilerdoesn’trequireit,classinterfaceandimplementationareusuallyintwodifferentfiles.Theinterfacefilemustbemadeavailabletoanyonewhousestheclass.Asinglefilecandeclareorimplementmorethanoneclass.Nevertheless,it’scustomarytohaveaseparateinterfacefileforeachclass,ifnotalsoaseparateimplementationfile.Keepingclassinterfaces
separatebetterreflectstheirstatusasindependententities.
Interfaceandimplementationfilestypicallyarenamedaftertheclass.Thenameoftheimplementationfilehas
the
.mextension,
indicatingthatitcontainsObjective-Csourcecode.Theinterfacefilecanbeassignedanyotherextension.Becauseit’sincludedinothersourcefiles,thenameoftheinterfacefileusuallyhasthe
.hextension
typicalofheaderfiles.Forexample,the
Rectangleclasswouldbedeclaredin
Rectangle.hand
definedin
Rectangle.m.
Separatinganobject’sinterfacefromitsimplementationfitswellwiththedesignofobject-orientedprograms.Anobjectisaself-containedentitythatcanbeviewedfromtheoutsidealmostasablackbox.
Onceyou’vedeterminedhowanobjectinteractswithotherelementsinyourprogram—thatis,onceyou’vedeclareditsinterface—youcanfreelyalteritsimplementationwithoutaffectinganyotherpartoftheapplication.
ClassInterface
Thedeclarationofaclassinterfacebeginswiththecompilerdirective@interfaceand
endswiththedirective
@end.
(AllObjective-Cdirectivestothecompilerbeginwith“@”.)
@interfaceClassName:ItsSuperclass |
//Methodandpropertydeclarations. |
@end |
Methodsandpropertiesfortheclassaredeclarednext,beforetheendoftheclassdeclaration.Thenames
ofmethodsthatcanbeusedbyclassobjects,classmethods,areprecededbyaplussign:
+alloc; |
aremarkedwithaminussign:
-(void)display; |
themethodreturnsthevalueinthevariable.Forexample,
Circlehasa
radiusmethodthat
couldmatcha
radiusinstancevariable.
MethodreturntypesaredeclaredusingthestandardCsyntaxforcastingonetypetoanother:
-(float)radius; |
-(void)setRadius:(float)aRadius; |
id.
The
allocmethodillustratedearlierreturns
id.
Whenthere’smorethanoneparameter,theparametersaredeclaredwithinthemethodnameafterthecolons.Parametersbreakthenameapartinthedeclaration,justasinamessage.Forexample:
-(void)setWidth:(float)widthheight:(float)height; |
ofparametersdeclarethemusingacommaandellipsispoints,justasafunctionwould:
-makeGroup:group,...; |
@property(attributes)TypepropertyName; |
Properties.”
Note:Historically,theinterfacerequireddeclarationsofaclass’sinstancevariables,
thedatastructuresthatarepartofeachinstanceoftheclass.Theseweredeclaredinbracesafterthe
@interfacedeclarationandbeforemethoddeclarations:
@interfaceClassName:ItsSuperclass |
{ |
//Instancevariabledeclarations. |
} |
//Methodandpropertydeclarations. |
@end |
declareinstancevariablesinthepublicinterfaceandsoyoushouldomitthebraces.
ImportingtheInterface
Theinterfacefilemustbeincludedinanysourcemodulethatdependsontheclassinterface—thatincludesanymodulethatcreatesaninstanceoftheclass,sendsamessagetoinvokeamethoddeclaredfortheclass,ormentionsaninstancevariabledeclaredintheclass.Theinterfaceisusuallyincludedwiththe
#importdirective:
#import"Rectangle.h" |
#include,
exceptthatitmakessurethatthesamefileisneverincludedmorethanonce.It’sthereforepreferredandisusedinplaceof
#includeincodeexamplesthroughoutObjective-C–based
documentation.
Toreflectthefactthataclassdefinitionbuildsonthedefinitionsofinheritedclasses,aninterfacefilebegins
byimportingtheinterfaceforitssuperclass:
#import"ItsSuperclass.h" |
@interfaceClassName:ItsSuperclass |
//Methodandpropertydeclarations. |
@end |
hierarchythattheclassisbuiltupon.
Notethatifthereisaprecomp—aprecompiledheader—thatsupportsthesuperclass,youmay
prefertoimporttheprecompinstead.
ReferringtoOtherClasses
Aninterfacefiledeclaresaclassand,byimportingitssuperclass,implicitlycontainsdeclarationsforallinheritedclasses,fromNSObjecton
downthroughitssuperclass.Iftheinterfacementionsclassesnotinthishierarchy,itmustimportthemexplicitlyordeclarethemwiththe
@classdirective:
@classRectangle,Circle; |
Aninterfacefilementionsclassnameswhenitstaticallytypesinstancevariables,returnvalues,andparameters.Forexample,thisdeclaration
-(void)setPrimaryColor:(NSColor*)aColor; |
NSColorclass.
Becausedeclarationslikethissimplyusetheclassnameasatypeanddon’tdependonanydetailsoftheclassinterface(itsmethodsandinstancevariables),the
@classdirective
givesthecompilersufficientforewarningofwhattoexpect.However,whentheinterfacetoaclassisactuallyused(instancescreated,messagessent),theclassinterfacemustbeimported.Typically,aninterfacefileuses
@classto
declareclasses,andthecorrespondingimplementationfileimportstheirinterfaces(sinceitneedstocreateinstancesofthoseclassesorsendthemmessages).
The
@classdirectiveminimizestheamountofcodeseenbythecompilerandlinker,andisthereforethesimplestwaytogive
aforwarddeclarationofaclassname.Beingsimple,itavoidspotentialproblemsthatmaycomewithimportingfilesthatimportstillotherfiles.Forexample,ifoneclassdeclaresastaticallytypedinstance
variableofanotherclass,andtheirtwointerfacefilesimporteachother,neitherclassmaycompilecorrectly.
TheRoleoftheInterface
Thepurposeoftheinterfacefileistodeclarethenewclasstoothersourcemodules(andtootherprogrammers).Itcontainsinformationtheyneedtoworkwiththeclass(programmersmightalsoappreciatealittledocumentation).
Theinterfacefiletellsusershowtheclassisconnectedintotheinheritancehierarchyandwhatotherclasses—inheritedorsimplyreferredtosomewhereintheclass—areneeded.
Throughitslistofmethoddeclarations,theinterfacefileletsothermodulesknowwhatmessagescanbesenttotheclassobjectandinstancesoftheclass.Everymethodthatcanbeused
outsidetheclassdefinitionisdeclaredintheinterfacefile;methodsthatareinternaltotheclassimplementationcanbeomitted.
ClassImplementation
Thedefinitionofaclassisstructuredverymuchlikeitsdeclaration.Itbeginswithan@implementationdirective
andendswiththe
@enddirective.
Inaddition,theclassmaydeclareinstancevariablesinbracesafterthe
@implementationdirective:
@implementationClassName |
{ |
//Instancevariabledeclarations. |
} |
//Methoddefinitions. |
@end |
Properties”).Ifyoudon’tdeclareadditionalinstancevariables,youcanomitthebraces:
@implementationClassName |
//Methoddefinitions. |
@end |
Rectangle.mimports
Rectangle.h.
Becausetheimplementationdoesn’tneedtorepeatanyofthedeclarationsitimports,itcansafelyomitthenameofthesuperclass.
Methodsfor
aclassaredefined,likeCfunctions,withinapairofbraces.Beforethebraces,they’redeclaredinthesamemannerasintheinterfacefile,butwithoutthesemicolon.Forexample:
+(id)alloc{ |
... |
} |
-(BOOL)isFilled{ |
... |
} |
-(void)setFilled:(BOOL)flag{ |
... |
} |
#import<stdarg.h> |
... |
-getGroup:group,...{ |
va_listap; |
va_start(ap,group); |
... |
} |
ReferringtoInstanceVariables
Bydefault,thedefinitionofaninstancemethodhasalltheinstancevariablesoftheobjectwithinitsscope.Itcanrefertothemsimplybyname.AlthoughthecompilercreatestheequivalentofCstructurestostoreinstancevariables,theexactnatureofthestructureishidden.Youdon’tneedeitherofthestructureoperators(
.or
->)
torefertoanobject’sdata.Forexample,thismethoddefinitionreferstothereceiver’s
filledinstancevariable:
-(void)setFilled:(BOOL)flag |
{ |
filled=flag; |
... |
} |
filledinstancevariableisdeclaredasaparametertothismethod,yettheinstancevariable
fallswithinitsscope.ThissimplificationofmethodsyntaxisasignificantshorthandinthewritingofObjective-Ccode.
Whentheinstancevariablebelongstoanobjectthat’snotthereceiver,theobject’stypemustbemadeexplicittothecompilerthroughstatictyping.
Inreferringtotheinstancevariableofastaticallytypedobject,thestructurepointeroperator(
->)isused.
Suppose,forexample,thatthe
Siblingclassdeclaresastaticallytypedobject,
twin,
asaninstancevariable:
@interfaceSibling:NSObject |
{ |
Sibling*twin; |
intgender; |
structfeatures*appearance; |
} |
twinis
typedtothesameclass),a
Siblingmethodcansetthemdirectly:
-makeIdenticalTwin |
{ |
if(!twin){ |
twin=[[Siblingalloc]init]; |
twin->gender=gender; |
twin->appearance=appearance; |
} |
returntwin; |
} |
TheScopeofInstanceVariables
Toenforcetheabilityofanobjecttohideitsdata,thecompilerlimitsthescopeofinstancevariables—thatis,limitstheirvisibilitywithintheprogram.Buttoprovideflexibility,italsoletsyouexplicitlysetthescopeatfourlevels.Eachlevelismarkedbyacompilerdirective:
Directive | Meaning |
---|---|
@private | Theinstancevariableisaccessibleonlywithintheclassthatdeclaresit. |
@protected | Theinstancevariableisaccessiblewithintheclassthatdeclaresitandwithinclassesthatinheritit.Allinstancevariableswithoutanexplicitscopedirectivehave@protectedscope. |
@public | Theinstancevariableisaccessibleeverywhere. |
@package | Usingthemodernruntime,an@packageinstancevariablehas @publicscopeinsidetheexecutable imagethatimplementstheclass,butactslike @privateoutside. The @packagescopeforObjective-Cinstancevariablesisanalogousto private_externfor Cvariablesandfunctions.Anycodeoutsidetheclassimplementation’simagethattriestousetheinstancevariablegetsalinkerror. Thisscopeismostusefulforinstancevariablesinframeworkclasses,where @privatemaybetoorestrictivebut @protectedor @publictoo permissive. |
Figure
2-1Thescopeofinstancevariables(
@packagescopenotshown)
Ascopingdirectiveappliestoalltheinstancevariableslistedafterit,uptothenextdirectiveortheendofthelist.Inthefollowingexample,the
ageand
evaluationinstance
variablesareprivate;
name,
job,and
wageare
protected;and
bossispublic.
@interfaceWorker:NSObject |
{ |
char*name; |
@private |
intage; |
char*evaluation; |
@protected |
idjob; |
floatwage; |
@public |
idboss; |
} |
nameabove)are
@protected.
Allinstancevariablesthataclassdeclares,nomatterhowthey’remarked,arewithinthescopeoftheclassdefinition.Forexample,aclassthatdeclaresa
jobinstance
variable,suchasthe
Workerclassshownabove,canrefertoitinamethoddefinition:
-promoteTo:newPosition |
{ |
idold=job; |
job=newPosition; |
returnold; |
} |
Normally,aclassalsohasaccesstotheinstancevariablesitinherits.Theabilitytorefertoaninstance
variableisusuallyinheritedalongwiththevariable.Itmakessenseforclassestohavetheirentiredatastructureswithintheirscope,especiallyifyouthinkofaclassdefinitionasmerelyanelaborationoftheclassesitinheritsfrom.The
promoteTo:method
illustratedearliercouldjustaswellhavebeendefinedinanyclassthatinheritsthe
jobinstancevariablefromthe
Workerclass.
However,therearereasonswhyyoumightwanttorestrictinheritingclassesfromdirectlyaccessinganinstancevariable:
Onceasubclassaccessesaninheritedinstancevariable,theclassthatdeclaresthevariableistiedtothatpartofitsimplementation.Inlaterversions,itcan’teliminatethevariable
oraltertheroleitplayswithoutinadvertentlybreakingthesubclass.
Moreover,ifasubclassaccessesaninheritedinstancevariableandaltersitsvalue,itmayinadvertentlyintroducebugsintheclassthatdeclaresthevariable,especiallyifthevariable
isinvolvedinclass-internaldependencies.
Tolimitaninstancevariable’sscopetojusttheclassthatdeclaresit,youmustmarkit
@private.Instancevariablesmarked
@privateare
onlyavailabletosubclassesbycallingpublicaccessormethods,iftheyexist.
Attheotherextreme,markingavariable
@publicmakesitgenerallyavailable,evenoutsideofclassdefinitionsthatinherit
ordeclarethevariable.Normally,togetinformationstoredinaninstancevariable,otherobjectsmustsendamessagerequestingit.However,apublicinstancevariablecanbeaccessedanywhereasifitwereafieldinaCstructure.Forexample:
Worker*ceo=[[Workeralloc]init]; |
ceo->boss=nil; |
Markinginstancevariables
@publicdefeatstheabilityofanobjecttohideitsdata.Itrunscountertoafundamentalprinciple
ofobject-orientedprogramming—theencapsulationofdatawithinobjectswhereit’sprotectedfromviewandinadvertenterror.Publicinstancevariablesshouldthereforebeavoidedexceptinextraordinarycases.
Messagestoselfandsuper
Objective-Cprovidestwotermsthatcanbeusedwithinamethoddefinitiontorefertotheobjectthatperformsthemethod—selfand
super.
Suppose,forexample,thatyoudefinea
repositionmethodthatneedstochangethecoordinatesofwhateverobjectitactson.
Itcaninvokethe
setOrigin::methodtomakethechange.Allitneedstodoissenda
setOrigin::message
tothesameobjectthatthe
repositionmessageitselfwassentto.Whenyou’rewritingtherepositioncode,youcanrefertothatobjectaseither
selfor
super.
The
repositionmethodcouldreadeither:
-reposition |
{ |
... |
[selfsetOrigin:someX:someY]; |
... |
} |
-reposition |
{ |
... |
[supersetOrigin:someX:someY]; |
... |
} |
selfand
superbothrefertotheobject
receivinga
repositionmessage,whateverobjectthatmayhappentobe.Thetwotermsarequitedifferent,however.
selfis
oneofthehiddenparametersthatthemessagingroutinepassestoeverymethod;it’salocalvariablethatcanbeusedfreelywithinamethodimplementation,justasthenamesofinstancevariablescanbe.
superis
atermthatsubstitutesfor
selfonlyasthereceiverinamessageexpression.Asreceivers,thetwotermsdifferprincipallyinhowtheyaffectthemessagingprocess:
selfsearches
forthemethodimplementationintheusualmanner,startinginthedispatchtableofthereceivingobject’sclass.Intheexampleabove,itwouldbeginwiththeclassoftheobjectreceivingtherepositionmessage.
superis
aflagthattellsthecompilertosearchforthemethodimplementationinaverydifferentplace.Itbeginsinthesuperclassoftheclassthatdefinesthemethodwhere
superappears.
Intheexampleabove,itwouldbeginwiththesuperclassoftheclasswhererepositionisdefined.
Wherever
superreceivesamessage,thecompilersubstitutesanothermessagingroutineforthe
objc_msgSendfunction.
Thesubstituteroutinelooksdirectlytothesuperclassofthedefiningclass—thatis,tothesuperclassoftheclasssendingthemessageto
super—ratherthantotheclass
oftheobjectreceivingthemessage.
AnExample:Usingselfandsuper
Thedifferencebetweenselfand
superbecomes
clearwhenusingahierarchyofthreeclasses.Suppose,forexample,thatwecreateanobjectbelongingtoaclasscalled
Low.Thesuperclassof
Lowis
Mid;
thesuperclassof
Midis
High.Allthreeclassesdefineamethodcalled
negotiate,
whicheachclassusesforitsownpurpose.Inaddition,
Middefinesanambitiousmethodcalled
makeLastingPeace,
whichitselfemploysthe
negotiatemethod.TheclassesandthosemethodsareillustratedinFigure
2-2.
Figure
2-2ThehierarchyofHigh,Mid,andLow
Supposethattheimplementationof
makeLastingPeace(inthe
Midclass)
uses
selftoindicatetheobjecttosendthe
negotiatemessageto:
-makeLastingPeace |
{ |
[selfnegotiate]; |
... |
} |
Lowobjecttoperformthe
makeLastingPeacemethod,
makeLastingPeacesends
a
negotiatemessagetothesame
Lowobject.Themessagingroutinefindstheversionof
negotiatedefined
in
Low,theclassof
self.
However,iftheimplementationof
makeLastingPeaceinsteaduses
superas
thereceiver,
-makeLastingPeace |
{ |
[supernegotiate]; |
... |
} |
negotiatedefinedin
High.
Itignorestheclass(
Low)oftheobjectthatreceivedthe
makeLastingPeacemessageand
skipstothesuperclassof
Mid,because
Midiswhere
makeLastingPeaceis
defined.Neitherimplementationfindsthe
Midversionof
negotiate.
Asthisexampleillustrates,
superprovidesawaytobypassamethodthatoverridesanothermethod.Here,theuseof
superenabled
makeLastingPeaceto
bypassthe
Midversionof
negotiatethatredefinedthe
Highversion
ofthatmethod.
Notbeingabletoreachthe
Midversionof
negotiate,
asjustdescribed,mayseemlikeaflaw,butunderthecircumstancesit’sintentional:
Theauthorofthe
Lowclassintentionallyoverrodethe
Midversion
of
negotiatesothatinstancesof
Low(anditssubclasses)wouldinvoketheredefinedversion
ofthemethodinstead.Thedesignerof
Lowdidn’twant
Lowobjectstoperformtheinherited
method.
Theauthorofthe
Midmethod
makeLastingPeace,
insendingthe
negotiatemessageto
super(asshowninthesecondimplementation),intentionally
skippedoverthe
Midversionof
negotiate(andoveranyversionsthatmightbedefinedin
classeslike
Lowthatinheritfrom
Mid)toperformtheversiondefinedinthe
Highclass.
Thedesignerofthesecondimplementationof
makeLastingPeacewantedtousethe
Highversion
of
negotiateandnoother.
The
Midversionof
negotiatecouldstill
beused,butitwouldtakeadirectmessagetoa
Midinstancetodoso.
Usingsuper
Messagestosuperallow
methodimplementationstobedistributedovermorethanoneclass.Youcanoverrideanexistingmethodtomodifyoraddtoitandstillincorporatetheoriginalmethodinthemodification:
-negotiate |
{ |
... |
return[supernegotiate]; |
} |
superfor
therest.The
initmethod,whichinitializesanewlyallocatedinstance,isdesignedtoworklikethis.Each
initmethod
hasresponsibilityforinitializingtheinstancevariables
definedinitsclass.Butbeforedoingso,itsendsan
initmessageto
supertohavethe
classesitinheritsfrominitializetheirinstancevariables.Eachversionof
initfollowsthisprocedure,soclassesinitializetheirinstancevariablesintheorderofinheritance:
-(id)init |
{ |
self=[superinit]; |
if(self){ |
... |
} |
} |
super.
Forexample,everyclassmethodthatcreatesaninstancemustallocatestorageforthenewobjectandinitializeits
isavariabletotheclassstructure.Allocationistypically
lefttothe
allocand
allocWithZone:methodsdefinedinthe
NSObjectclass.
Ifanotherclassoverridesthesemethods(ararecase),itcanstillgetthebasicfunctionalitybysendingamessageto
super.
Redefiningself
superissimplyaflagtothecompilertellingitwheretobeginsearchingforthemethodtoperform;it’susedonlyasthe
receiverofamessage.But
selfisavariablenamethatcanbeusedin
anynumberofways,evenassignedanewvalue.
There’satendencytodojustthatindefinitionsofclassmethods.Classmethodsareoftenconcernednotwiththeclassobject,butwithinstancesoftheclass.Forexample,manyclassmethodscombineallocation
andinitializationofaninstance,oftensettingupinstancevariablevaluesatthesametime.Insuchamethod,itmightbetemptingtosendmessagestothenewlyallocatedinstanceandtocalltheinstance
self,
justasinaninstancemethod.Butthatwouldbeanerror.
selfand
superbothreferto
thereceivingobject—theobjectthatgetsamessagetellingittoperformthemethod.Insideaninstancemethod,
selfreferstotheinstance;butinsideaclassmethod,
selfrefers
totheclassobject.Thisisanexampleofwhatnottodo:
+(Rectangle*)rectangleOfColor:(NSColor*)color |
{ |
self=[[Rectanglealloc]init];//BAD |
[selfsetColor:color]; |
returnself; |
} |
selftorefertoaninstanceinsideaclassmethod:
+(id)rectangleOfColor:(NSColor*)color |
{ |
idnewInstance=[[Rectanglealloc]init];//GOOD |
[newInstancesetColor:color]; |
returnnewInstance; |
} |
allocmessagetotheclassinaclassmethod,it’softenbettertosend
allocto
self.
Thisway,iftheclassissubclassed,andthe
rectangleOfColor:messageisreceivedbyasubclass,theinstancereturnedisthesametypeasthesubclass(forexample,the
arraymethod
of
NSArrayisinheritedby
NSMutableArray).
+(id)rectangleOfColor:(NSColor*)color |
{ |
idnewInstance=[[selfalloc]init];//EXCELLENT |
[newInstancesetColor:color]; |
returnnewInstance; |
} |
andrelatedmethods.
相关文章推荐
- Objective-C 编程语言官网文档(三)-如何定义类
- Objective-C 编程语言官网文档(二)-对象,类以及消息
- Objective-C 编程语言官网文档(九)-静态行为
- Objective-C 编程语言官网文档(十二)-线程
- Objective-C 编程语言官网文档(四)-协议
- Objective-C 编程语言官网文档(五)-属性的声明
- Objective-C 编程语言官网文档(九)-静态行为
- Objective-C 编程语言官网文档(七)-关联引用
- Objective-C 编程语言官网文档(十一)-异常的处理
- Objective-C 编程语言官网文档(一)-简介
- Objective-C 编程语言官网文档(十一)-异常的处理
- Objective-C 编程语言官网文档(十三 终结篇)-词汇表
- Objective-C 编程语言官网文档(一)-简介
- Objective-C 编程语言官网文档(十三 终结篇)-词汇表
- Objective-C 编程语言官网文档(八)-快速枚举
- Objective-C 编程语言官网文档(十)-选择器
- Objective-C 编程语言官网文档(五)-属性的声明
- Objective-C 编程语言官网文档(十)-选择器
- Objective-C 编程语言官网文档(五)-属性的声明
- Objective-C 编程语言官网文档(六)-类别以及扩展