继承、多型(Inheritance, Polymorphism)以及其他物件导向功能
id 型别
Objective-C 有种叫做 id 的型别,它的运作有时候像是 void*,不过它却严格规定只能用在物件。Objective-C 与 Java 跟 C++ 不一样,你在唿叫一个物件的 method 时,并不需要知道这个物件的型别。当然这个 method 一定要存在,这称为 Objective-C 的讯息传递。
- Fraction.h
- #import
- @interfaceFraction:NSObject{
- intnumerator;
- intdenominator;
- }
- -(Fraction*)initWithNumerator:(int)ndenominator:(int)d;
- -(void)print;
- -(void)setNumerator:(int)d;
- -(void)setDenominator:(int)d;
- -(void)setNumerator:(int)nandDenominator:(int)d;
- -(int)numerator;
- -(int)denominator;
- @end
- Fraction.m
- #import"Fraction.h"
- #import
- @implementationFraction
- -(Fraction*)initWithNumerator:(int)ndenominator:(int)d{
- self=[superinit];
- if(self){
- [selfsetNumerator:nandDenominator:d];
- }
- returnself;
- }
- -(void)print{
- printf("%i/%i",numerator,denominator);
- }
- -(void)setNumerator:(int)n{
- nnumerator=n;
- }
- -(void)setDenominator:(int)d{
- ddenominator=d;
- }
- -(void)setNumerator:(int)nandDenominator:(int)d{
- nnumerator=n;
- ddenominator=d;
- }
- -(int)denominator{
- returndenominator;
- }
- -(int)numerator{
- returnnumerator;
- }
- @end
- Complex.h
- #import
- @interfaceComplex:NSObject{
- doublereal;
- doubleimaginary;
- }
- -(Complex*)initWithReal:(double)randImaginary:(double)i;
- -(void)setReal:(double)r;
- -(void)setImaginary:(double)i;
- -(void)setReal:(double)randImaginary:(double)i;
- -(double)real;
- -(double)imaginary;
- -(void)print;
- @end
- Complex.m
- #import"Complex.h"
- #import
- @implementationComplex
- -(Complex*)initWithReal:(double)randImaginary:(double)i{
- self=[superinit];
- if(self){
- [selfsetReal:randImaginary:i];
- }
- returnself;
- }
- -(void)setReal:(double)r{
- rreal=r;
- }
- -(void)setImaginary:(double)i{
- iimaginary=i;
- }
- -(void)setReal:(double)randImaginary:(double)i{
- rreal=r;
- iimaginary=i;
- }
- -(double)real{
- returnreal;
- }
- -(double)imaginary{
- returnimaginary;
- }
- -(void)print{
- printf("%_f+%_fi",real,imaginary);
- }
- @end
- main.m
- #import
- #import"Fraction.h"
- #import"Complex.h"
- intmain(intargc,constchar*argv[]){
- //createanewinstance
- Fraction*frac=[[Fractionalloc]initWithNumerator:1denominator:10];
- Complex*comp=[[Complexalloc]initWithReal:10andImaginary:15];
- idnumber;
- //printfraction
- number=frac;
- printf("Thefractionis:");
- [numberprint];
- printf("\n");
- //printcomplex
- number=comp;
- printf("Thecomplexnumberis:");
- [numberprint];
- printf("\n");
- //freememory
- [fracrelease];
- [comprelease];
- return0;
- }
output
- Thefractionis:1/10
- Thecomplexnumberis:10.000000+15.000000i
这种动态连结有显而易见的好处。你不需要知道你唿叫 method 的那个东西是什么型别,如果这个物件对这个讯息有反应,那就会唤起这个 method。这也不会牵涉到一堆繁琐的转型动作,比如在 Java 裡唿叫一个整数物件的 .intValue() 就得先转型,然后才能唿叫这个 method。
继承(Inheritance)
- Rectangle.h
- #import
- @interfaceRectangle:NSObject{
- intwidth;
- intheight;
- }
- -(Rectangle*)initWithWidth:(int)wheight:(int)h;
- -(void)setWidth:(int)w;
- -(void)setHeight:(int)h;
- -(void)setWidth:(int)wheight:(int)h;
- -(int)width;
- -(int)height;
- -(void)print;
- @end
- Rectangle.m
- #import"Rectangle.h"
- #import
- @implementationRectangle
- -(Rectangle*)initWithWidth:(int)wheight:(int)h{
- self=[superinit];
- if(self){
- [selfsetWidth:wheight:h];
- }
- returnself;
- }
- -(void)setWidth:(int)w{
- wwidth=w;
- }
- -(void)setHeight:(int)h{
- hheight=h;
- }
- -(void)setWidth:(int)wheight:(int)h{
- wwidth=w;
- hheight=h;
- }
- -(int)width{
- returnwidth;
- }
- -(int)height{
- returnheight;
- }
- -(void)print{
- printf("width=%i,height=%i",width,height);
- }
- @end
- Square.h
- #import"Rectangle.h"
- @interfaceSquare:Rectangle
- -(Square*)initWithSize:(int)s;
- -(void)setSize:(int)s;
- -(int)size;
- @end
- Square.m
- #import"Square.h"
- @implementationSquare
- -(Square*)initWithSize:(int)s{
- self=[superinit];
- if(self){
- [selfsetSize:s];
- }
- returnself;
- }
- -(void)setSize:(int)s{
- width=s;
- height=s;
- }
- -(int)size{
- returnwidth;
- }
- -(void)setWidth:(int)w{
- [selfsetSize:w];
- }
- -(void)setHeight:(int)h{
- [selfsetSize:h];
- }
- @end
- main.m
- #import"Square.h"
- #import"Rectangle.h"
- #import
- intmain(intargc,constchar*argv[]){
- Rectangle*rec=[[Rectanglealloc]initWithWidth:10height:20];
- Square*sq=[[Squarealloc]initWithSize:15];
- //printem
- printf("Rectangle:");
- [recprint];
- printf("\n");
- printf("Square:");
- [sqprint];
- printf("\n");
- //updatesquare
- [sqsetWidth:20];
- printf("Squareafterchange:");
- [sqprint];
- printf("\n");
- //freememory
- [recrelease];
- [sqrelease];
- return0;
- }
output
- Rectangle:width=10,height=20
- Square:width=15,height=15
- Squareafterchange:width=20,height=20
继承在 Objective-C 裡比较像 Java。当你扩充你的 super class(所以只能有一个 parent),你想自订这个 super class 的 method,只要简单的在你的 child class implementation 裡放上新的实作内容即可。而不需要 C++ 裡呆呆的 virtual table。
这裡还有一个值得玩味的地方,如果你企图像这样去唿叫 rectangle 的 constructor: Square *sq = [[Square alloc] initWithWidth: 10 height: 15],会发生什么事?答案是会产生一个编译器错误。因为 rectangle constructor 回传的型别是 Rectangle*,而不是 Square*,所以这行不通。在某种情况下如果你真想这样用,使用 id 型别会是很好的选择。如果你想使用 parent 的 constructor,只要把 Rectangle* 回传型别改成 id 即可。
动态识别(Dynamic types)
这裡有一些用于 Objective-C 动态识别的 methods(说明部分採中英并列,因为我觉得英文比较传神,中文怎么译都怪):
- -(BOOL)isKindOfClass:classObjisobjectadescendentormemberofclassObj
此物件是否是 classObj 的子孙或一员
- -(BOOL)isMemberOfClass:classObjisobjectamemberofclassObj
此物件是否是 classObj 的一员
- -(BOOL)respondsToSelector:selectordoestheobjecthaveamethodnamedspecifiecbytheselector
此物件是否有叫做 selector 的 method
- +(BOOL)instancesRespondToSelector:selectordoesanobjectcreatedbythisclasshavetheabilitytorespondtothespecifiedselector
此物件是否是由有能力回应指定 selector 的物件所产生
- -(id)performSelector:selectorinvokethespecifiedselectorontheobject
唤起此物件的指定 selector
所有继承自 NSObject 都有一个可回传一个 class 物件的 class method。这非常近似于 Java 的 getClass() method。这个 class 物件被使用于前述的 methods 中。
Selectors 在 Objective-C 用以表示讯息。下一个範例会秀出建立 selector 的语法。
- main.m
- #import"Square.h"
- #import"Rectangle.h"
- #import
- intmain(intargc,constchar*argv[]){
- Rectangle*rec=[[Rectanglealloc]initWithWidth:10height:20];
- Square*sq=[[Squarealloc]initWithSize:15];
- //isMemberOfClass
- //true
- if([sqisMemberOfClass:[Squareclass]]==YES){
- printf("squareisamemberofsquareclass\n");
- }
- //false
- if([sqisMemberOfClass:[Rectangleclass]]==YES){
- printf("squareisamemberofrectangleclass\n");
- }
- //false
- if([sqisMemberOfClass:[NSObjectclass]]==YES){
- printf("squareisamemberofobjectclass\n");
- }
- //isKindOfClass
- //true
- if([sqisKindOfClass:[Squareclass]]==YES){
- printf("squareisakindofsquareclass\n");
- }
- //true
- if([sqisKindOfClass:[Rectangleclass]]==YES){
- printf("squareisakindofrectangleclass\n");
- }
- //true
- if([sqisKindOfClass:[NSObjectclass]]==YES){
- printf("squareisakindofobjectclass\n");
- }
- //respondsToSelector
- //true
- if([sqrespondsToSelector:@selector(setSize:)]==YES){
- printf("squarerespondstosetSize:method\n");
- }
- //false
- if([sqrespondsToSelector:@selector(nonExistant)]==YES){
- printf("squarerespondstononExistantmethod\n");
- }
- //true
- if([SquarerespondsToSelector:@selector(alloc)]==YES){
- printf("squareclassrespondstoallocmethod\n");
- }
- //instancesRespondToSelector
- //false
- if([RectangleinstancesRespondToSelector:@selector(setSize:)]==YES){
- printf("rectangleinstancerespondstosetSize:method\n");
- }
- //true
- if([SquareinstancesRespondToSelector:@selector(setSize:)]==YES){
- printf("squareinstancerespondstosetSize:method\n");
- }
- //freememory
- [recrelease];
- [sqrelease];
- return0;
- }
output
- squareisamemberofsquareclass
- squareisakindofsquareclass
- squareisakindofrectangleclass
- squareisakindofobjectclass
- squarerespondstosetSize:method
- squareclassrespondstoallocmethod
- squareinstancerespondstosetSize:method
- Categories
当你想要为某个 class 新增 methods,你通常会扩充(extend,即继承)它。然而这不一定是个完美解法,特别是你想要重写一个 class 的某个功能,但你却没有塬始码时。Categories 允许你在现有的 class 加入新功能,但不需要扩充它。Ruby 语言也有类似的功能。
- FractionMath.h
- #import"Fraction.h"
- @interfaceFraction(Math)
- -(Fraction*)add:(Fraction*)f;
- -(Fraction*)mul:(Fraction*)f;
- -(Fraction*)div:(Fraction*)f;
- -(Fraction*)sub:(Fraction*)f;
- @end
- FractionMath.m
- #import"FractionMath.h"
- @implementationFraction(Math)
- -(Fraction*)add:(Fraction*)f{
- return[[Fractionalloc]initWithNumerator:numerator*[fdenominator]+
- denominator*[fnumerator]
- denominator:denominator*[fdenominator]];
- }
- -(Fraction*)mul:(Fraction*)f{
- return[[Fractionalloc]initWithNumerator:numerator*[fnumerator]
- denominator:denominator*[fdenominator]];
- }
- -(Fraction*)div:(Fraction*)f{
- return[[Fractionalloc]initWithNumerator:numerator*[fdenominator]
- denominator:denominator*[fnumerator]];
- }
- -(Fraction*)sub:(Fraction*)f{
- return[[Fractionalloc]initWithNumerator:numerator*[fdenominator]-
- denominator*[fnumerator]
- denominator:denominator*[fdenominator]];
- }
- @end
- main.m
- #import
- #import"Fraction.h"
- #import"FractionMath.h"
- intmain(intargc,constchar*argv[]){
- //createanewinstance
- Fraction*frac1=[[Fractionalloc]initWithNumerator:1denominator:3];
- Fraction*frac2=[[Fractionalloc]initWithNumerator:2denominator:5];
- Fraction*frac3=[frac1mul:frac2];
- //printit
- [frac1print];
- printf("*");
- [frac2print];
- printf("=");
- [frac3print];
- printf("\n");
- //freememory
- [frac1release];
- [frac2release];
- [frac3release];
- return0;
- }
output
- 1/3*2/5=2/15
重点是 @implementation 跟 @interface 这两行:@interface Fraction (Math) 以及 @implementation Fraction (Math).
(同一个 class)只能有一个同名的 category,其他的 categories 得加上不同的、独一无二的名字。
Categories 在建立 private methods 时十分有用。因为 Objective-C 并没有像 Java 这种 private/protected/public methods 的概念,所以必须要使用 categories 来达成这种功能。作法是把 private method 从你的 class header (.h) 档案移到 implementation (.m) 档案。以下是此种作法一个简短的範例。
- MyClass.h
- #import
- @interfaceMyClass:NSObject
- -(void)publicMethod;
- @end
- MyClass.m
- #import"MyClass.h"
- #import
- @implementationMyClass
- -(void)publicMethod{
- printf("publicmethod\n");
- }
- @end
- //privatemethods
- @interfaceMyClass(Private)
- -(void)privateMethod;
- @end
- @implementationMyClass(Private)
- -(void)privateMethod{
- printf("privatemethod\n");
- }
- @end
- main.m
- #import"MyClass.h"
- intmain(intargc,constchar*argv[]){
- MyClass*obj=[[MyClassalloc]init];
- //thiscompiles
- [objpublicMethod];
- //thisthrowserrorswhencompiling
- //[objprivateMethod];
- //freememory
- [objrelease];
- return0;
- }
output
- publicmethod
- Posing
Posing 有点像 categories,但是不太一样。它允许你扩充一个 class,并且全面性地的扮演(pose)这个 super class。例如:你有一个扩充 NSArray 的 NSArrayChild 物件。如果你让 NSArrayChild 扮演 NSArray,则在你的程式码中所有的 NSArray 都会自动被替代为 NSArrayChild。
- FractionB.h
- #import"Fraction.h"
- @interfaceFractionB:Fraction
- -(void)print;
- @end
- FractionB.m
- #import"FractionB.h"
- #import
- @implementationFractionB
- -(void)print{
- printf("(%i/%i)",numerator,denominator);
- }
- @end
- main.m
- #import
- #import"Fraction.h"
- #import"FractionB.h"
- intmain(intargc,constchar*argv[]){
- Fraction*frac=[[Fractionalloc]initWithNumerator:3denominator:10];
- //printit
- printf("Thefractionis:");
- [fracprint];
- printf("\n");
- //makeFractionBposeasFraction
- [FractionBposeAsClass:[Fractionclass]];
- Fraction*frac2=[[Fractionalloc]initWithNumerator:3denominator:10];
- //printit
- printf("Thefractionis:");
- [frac2print];
- printf("\n");
- //freememory
- [fracrelease];
- [frac2release];
- return0;
- }
output
- Thefractionis:3/10
- Thefractionis:(3/10)
这个程式的输出中,第一个 fraction 会输出 3/10,而第二个会输出 (3/10)。这是 FractionB 中实作的方式。
poseAsClass 这个 method 是 NSObject 的一部份,它允许 subclass 扮演 superclass。
Protocols
Objective-C 裡的 Protocol 与 Java 的 interface 或是 C++ 的 purely virtual class 相同。
- Printing.h
- @protocolPrinting
- -(void)print;
- @end
- Fraction.h
- #import
- #import"Printing.h"
- @interfaceFraction:NSObject{
- intnumerator;
- intdenominator;
- }
- -(Fraction*)initWithNumerator:(int)ndenominator:(int)d;
- -(void)setNumerator:(int)d;
- -(void)setDenominator:(int)d;
- -(void)setNumerator:(int)nandDenominator:(int)d;
- -(int)numerator;
- -(int)denominator;
- @end
- Fraction.m
- #import"Fraction.h"
- #import
- @implementationFraction
- -(Fraction*)initWithNumerator:(int)ndenominator:(int)d{
- self=[superinit];
- if(self){
- [selfsetNumerator:nandDenominator:d];
- }
- returnself;
- }
- -(void)print{
- printf("%i/%i",numerator,denominator);
- }
- -(void)setNumerator:(int)n{
- nnumerator=n;
- }
- -(void)setDenominator:(int)d{
- ddenominator=d;
- }
- -(void)setNumerator:(int)nandDenominator:(int)d{
- nnumerator=n;
- ddenominator=d;
- }
- -(int)denominator{
- returndenominator;
- }
- -(int)numerator{
- returnnumerator;
- }
- -(Fraction*)copyWithZone:(NSZone*)zone{
- return[[FractionallocWithZone:zone]initWithNumerator:numerator
- denominator:denominator];
- }
- @end
- Complex.h
- #import
- #import"Printing.h"
- @interfaceComplex:NSObject{
- doublereal;
- doubleimaginary;
- }
- -(Complex*)initWithReal:(double)randImaginary:(double)i;
- -(void)setReal:(double)r;
- -(void)setImaginary:(double)i;
- -(void)setReal:(double)randImaginary:(double)i;
- -(double)real;
- -(double)imaginary;
- @end
- Complex.m
- #import"Complex.h"
- #import
- @implementationComplex
- -(Complex*)initWithReal:(double)randImaginary:(double)i{
- self=[superinit];
- if(self){
- [selfsetReal:randImaginary:i];
- }
- returnself;
- }
- -(void)setReal:(double)r{
- rreal=r;
- }
- -(void)setImaginary:(double)i{
- iimaginary=i;
- }
- -(void)setReal:(double)randImaginary:(double)i{
- rreal=r;
- iimaginary=i;
- }
- -(double)real{
- returnreal;
- }
- -(double)imaginary{
- returnimaginary;
- }
- -(void)print{
- printf("%_f+%_fi",real,imaginary);
- }
- @end
- main.m
- #import
- #import"Fraction.h"
- #import"Complex.h"
- intmain(intargc,constchar*argv[]){
- //createanewinstance
- Fraction*frac=[[Fractionalloc]initWithNumerator:3denominator:10];
- Complex*comp=[[Complexalloc]initWithReal:5andImaginary:15];
- idprintable;
- idcopyPrintable;
- //printit
- printable=frac;
- printf("Thefractionis:");
- [printableprint];
- printf("\n");
- //printcomplex
- printable=comp;
- printf("Thecomplexnumberis:");
- [printableprint];
- printf("\n");
- //thiscompilesbecauseFractioncomformstobothPrintingandNSCopyable
- copyPrintable=frac;
- //thisdoesn'tcompilebecauseComplexonlyconformstoPrinting
- //copyPrintable=comp;
- //testconformance
- //true
- if([fracconformsToProtocol:@protocol(NSCopying)]==YES){
- printf("FractionconformstoNSCopying\n");
- }
- //false
- if([compconformsToProtocol:@protocol(NSCopying)]==YES){
- printf("ComplexconformstoNSCopying\n");
- }
- //freememory
- [fracrelease];
- [comprelease];
- return0;
- }
output
- Thefractionis:3/10
- Thecomplexnumberis:5.000000+15.000000i
- FractionconformstoNSCopying
protocol 的宣告十分简单,基本上就是 @protocol ProtocolName (methods you must implement) @end。
要遵从(conform)某个 protocol,将要遵从的 protocols 放在 <> 裡面,并以逗点分隔。如:@interface SomeClass
protocol 要求实作的 methods 不需要放在 header 档裡面的 methods 列表中。如你所见,Complex.h 档案裡没有 -(void) print 的宣告,却还是要实作它,因为它(Complex class)遵从了这个 protocol。
Objective-C 的介面系统有一个独一无二的观念是如何指定一个型别。比起 C++ 或 Java 的指定方式,如:Printing *someVar = ( Printing * ) frac; 你可以使用 id 型别加上 protocol:id var = frac;。这让你可以动态地指定一个要求多个 protocol 的型别,却从头到尾只用了一个变数。如: var = frac;
就像使用@selector 来测试物件的继承关係,你可以使用 @protocol 来测试物件是否遵从介面。如果物件遵从这个介面,[object conformsToProtocol: @protocol( SomeProtocol )] 会回传一个 YES 型态的 BOOL 物件。同样地,对 class 而言也能如法炮製 [SomeClass conformsToProtocol: @protocol( SomeProtocol )]。