目录:
第1 页:设置环境
第2 页:创建类
第3页:详细说明
第4 页:继承、多态性和其他面向对象的特性
第8 页:内存管理
第9 页:基础框架类
本初学者指南的所有源代码都可以从objc.tar.gz 下载。本教程中的许多示例均由Steve Kochan 在Objective-C 编程一书中编写。如果您想要更详细的信息和示例,请直接参阅本书。本网站发布的所有示例均已获得他的许可,因此请勿复制或转载。
设置环境
Linux/FreeBSD:安装GNUStep
为了编译GNUstep 应用程序,必须首先执行位于/usr/GNUstep/System/Makefiles/GNUstep.sh 的GNUstep.sh 文件。这个路径取决于你的系统环境,有的在/usr,有的在/usr/lib,有的在/usr/local。如果您的shell 是基于csh/tcsh 的shell,则应该使用GNUStep.csh。建议将此指令放在.bashrc 或.cshrc 中。
Mac OS X:安装XCode
Windows NT 5.X:安装cygwin 或mingw,然后安装GNUStep
前言
本教程假设您已经具备一些C 语言的基础知识,包括C 数据类型、什么是函数、什么是返回值、有关指标的知识以及基本的C 语言内存管理。如果你没有这方面的背景知识,我强烈推荐你读一下KR的书:《The CProgramming Language》(译注:台湾刊物《CProgrammingLanguageSecondEdition》)。这是C语言设计者写的一本书。
Objective-C是C的衍生语言,继承了C语言的所有特性。有一些例外,但它们并不是从C 本身继承的语言特性。
nil:在C/C++中你可能使用过NULL,但在Objective-C中它是nil。不同之处在于你可以将消息传递给nil(例如[nil message];),这是完全合法的,但你不能对NULL 做同样的事情。
BOOL:C 没有正式的布尔类型,并且Objective-C 中也没有“真正”的布尔类型。它包含在基础类中(即import NSObject.h;nil 也包含在此头文件中)。 BOOL 在Objective-C 中有两种类型:YES 或NO,而不是TRUE 或FALSE。
#import 与#include:正如您在hello world 示例中看到的,我们使用了#import。 gcc 编译器支持#import。我不建议使用#include。 #import 与.h 文件开头和结尾处的#ifndef #define #endif 基本相同。许多程序员都认为使用这些东西是愚蠢的。无论如何,只需使用#import。这不仅可以避免麻烦,而且如果有一天gcc 将其拿走,将会有足够多的Objective-C 程序员坚持保留它或将其放回去。偷偷告诉你,Apple在他们的官方代码中也使用了#import。所以如果有一天出现这种情况,不难预测苹果会提供支持#import的gcc分支版本。
在Objective-C 中,方法和消息这两个词可以互换。然而,消息有其特殊性。消息可以动态转发到另一个对象。在Objective-C 中,调用对象上的消息并不一定意味着该对象会实际实现该消息,而是该对象知道如何以某种方式实现它,或者将其转发给知道如何实现它的对象。
编译hello worldhello.m#importintmain(intargc,constchar*argv[]){printf('helloworld
');返回0;}
输出
你好世界
在Objective-C 中使用#import 代替#include
Objective-C 的默认文件扩展名是.m
#p#字幕#e#
创建课程
@interface Fraction.h#import@interfaceFraction:NSObject{intnumerator;intdenominator;}-(void)print;-(void)setNumerator:(int)n;-(void)setDenominator:(int)d;-(int)分子;-(int)分母;@end
NSObject:NeXTStep 对象的缩写。由于它已更名为OpenStep,因此今天这没有多大意义。
继承是用Class:Parent来表示的,就像上面的Fraction:NSObject一样。
那些夹在@interface ClassParent { . } 中的称为实例变量。
当未设置访问权限(受保护、公共、私有)时,默认访问权限受到保护。稍后会解释如何设置权限。
实例方法跟随成员变量(即实例变量)。格式为:scope(returnType)methodName:(parameter1Type)parameter1Name;
有两种类型的作用域:类或实例。实例方法以- 开头,类级别方法以+ 开头。
接口以@end 结束。
@implementationFraction.m#import'Fraction.h'#import@implementationFraction-(void)print{printf('%i/%i',分子,分母);}-(void)setNumerator:(int)n{nnumerator=n;}-(void)设置分母:(int)d{d分母=d;}-(int)分母{return分母;}-(int)分子{returnnumerator;}@end
实现以@implementation ClassName 开始,以@end 结束。
Implement 定义方法的方式与在接口中声明方法的方式非常相似。
将它们放在一起main.m#import#import'Fraction.h'intmain(intargc,constchar*argv[]){//createanewinstanceFraction*frac=[[Fractionalloc]init];//setthevalues[fracsetNumerator:1]; [fracsetDenominator:3];//printitprintf('分数是:');[fracprint];printf('
');//freememory[fracrelease];return0;}
输出分数为:1/3Fraction*frac=[[Fractionalloc]init];
这行代码中有很多重要的东西。
Objective-C 中称为methods 的方法是[object method],就像C++ 中的object-method() 一样。
Objective-C 没有值类型。所以不存在C++ 的Fraction frac 这样的东西;压裂打印(); Objective-C 中完全使用指针来处理对象。
这行代码实际上做了两件事: [Fraction alloc] 调用Fraction 类的alloc 方法。就像malloc 内存一样,这个动作做同样的事情。
[object init] 是一个构造函数调用,负责初始化对象中的所有变量。它对[Fraction alloc] 返回的实例调用init 方法。这一操作非常常见,通常只需一行即可完成:Object *var=[[Object alloc] init];
[frac setNumerator1] 非常简单。它调用frac 的setNumerator 方法并传入1 作为参数。
与C 的每个变体一样,Objective-C 也有一种释放内存的方法:release。它继承自NSObject。稍后将详细解释该方法。
#p#字幕#e#
多个参数
到目前为止我还没有展示如何传递多个参数。这个语法乍一看似乎不太直观,但它来自一个非常流行的Smalltalk 版本。 Fraction.h.-(void)setNumerator:(int)nandDenominator:(int)d;Fraction.m.-(void)setNumerator:(int)nandDenominator:(int)d{nnumerator=n ;ddenominator=d;}.main.m#import#import'Fraction.h'intmain(intargc,constchar*argv[]){//createanewinstanceFraction*frac=[[Fractionalloc]init];Fraction*frac2=[ [Fractionalloc]init];//设置值[fracsetNumerator:1];[fracsetDenominator:3];//combinedset[frac2setNumerator:1andDenominator:5];//printitprintf('Thefractionis:');[fracprint];printf('
');//printitprintf('Fraction2is:');[frac2print];printf('
');//freememory[fracrelease];[frac2release];return0;}
输出分数为:1/3分数2为:1/5
这个方法实际上调用的是setNumerator:andDenominator:
添加其他参数的方法与添加第二个相同,即method:label1:label2:label3:调用方法为[obj methodparam1 label1param2 label2param3 label3param4]
标签是可选的,因此您可以使用如下方法:method:它只是省略标签名称,但用: 分隔参数。不建议使用此方法。
构造函数(Constructors)Fraction.h.-(Fraction*)initWithNumerator:(int)n 分母:(int)d;Fraction.m.-(Fraction*)initWithNumerator:(int)ndenominator:( int )d{self=[superinit];if(self){[selfsetNumerator:nandDenominator:d];}returnself;}.main.m#import#import'Fraction.h'intmain(intargc,constchar*argv[ ] ){//createanewinstanceFraction*frac=[[Fractionalloc]init];Fraction*frac2=[[Fractionalloc]init];Fraction*frac3=[[Fractionalloc]initWithNumerator:3denominator:10];//设置值[fracsetNumerator:1] ; [fracsetDenominator:3];//combinedset[frac2setNumerator:1andDenominator:5];//printitprintf('Thefractionis:');[fracprint];printf('
');printf('Fraction2is:');[frac2print];printf('
');printf('Fraction3is:');[frac3print];printf('
');//释放内存[fracrelease];[frac2release];[frac3release];return0;}
输出分数为:1/3分数2为:1/5分数3为:3/10
@interface 中的声明就像普通函数一样。
@implementation 使用新关键字:super
与Java 一样,Objective-C 只有一个父类。
使用[super init] 访问Super 构造函数。此操作需要适当的继承设计。
您将此操作返回的实例分配给另一个新关键字:self。 Self 很像C++ 和Java 中的this 指示器。
if ( self ) 与( self !=nil ) 相同,是为了保证超级构造函数成功返回一个新对象。 nil 是Objective-C 在C/C++ 中表达NULL 的方式,可以通过引入NSObject 来获得。
初始化变量后,您可以通过返回self 来传回您自己的地址。
默认构造函数是-(id) init。
从技术上讲,Objective-C 中的构造函数是一个“init”方法,与具有特殊结构的C++ 和Java 不同。
访问权
默认权限是@protected
Java的实现是在方法和变量前面添加public/private/protected修饰符,而Objective-C的做法更像是C++对实例变量的做法(译注:C++术语一般称为数据成员)。 Access.h#import@interfaceAccess:NSObject{@publicintpublicVar;@privateintprivateVar;intprivateVar2;@protectedintprotectedVar;}@endAccess.m#import'Access.h'@implementationAccess@endmain.m#import'Access.h'#importintmain(intargc ,constchar*argv[]){Access*a=[[Accessalloc]init];//worksa-publicVar=5;printf('publicvar:%i
',a-publicVar);//不编译//a-privateVar=10;//printf('privatevar:%i
',a-privateVar);[arelease];return0;}
输出公共变量:5
可以看到,它就像C++中private[list of vars] public[list of vars]的格式一样,只是改为@private、@protected等。
类级别accessClassA.h#importstaticintcount;@interfaceClassA:NSObject+(int)initCount;+(void)initialize;@endClassA.m#import'ClassA.h'@implementationClassA-(id)init{self=[superinit];count++; returnself;}+(int)initCount{returncount;}+(void)initialize{count=0;}@endmain.m#import'ClassA.h'#importintmain(intargc,constchar*argv[]){ClassA*c1=[[ClassAalloc]init];ClassA*c2=[[ClassAalloc]init];//printcountprintf('ClassAcount:%i
',[ClassAinitCount]);ClassA*c3=[[ClassAalloc]init];//再次打印计数printf('ClassAcount:%i
',[ClassAinitCount]);[c1release];[c2release];[c3release];return0;}
输出ClassAcount:2ClassAcount:3
静态整数计数=0;这就是类变量的声明方式。事实上,把这样的变量放在这里并不理想。更好的解决方案是在Java 中实现静态类变量。然而,它确实有效。
+(int) 初始化计数;这是返回计数值的实际方法。注意细微的差别!我们在类型前面使用加号+,而不是使用减号-。加号+ 表示这是一个类级别的函数。 (译注:在很多文档中,类级函数被称为类函数或类方法)
访问这个变量和访问普通成员变量没有什么区别,就像ClassA中的count++用法一样。
+(void) 初始化方法在Objective-C 开始执行程序时被调用,并且每个类也会被调用。这是初始化类级别变量(例如我们的计数)的好地方。
例外情况
注意:仅Mac OS X 10.3 及更高版本支持异常处理。 CupWarningException.h#import@interfaceCupWarningException:NSEXCEPTION@EndcupWarningException.m#Import'cupwarningException.h'@Implementationc UpwarningException@EndcupoverflowException.h#Import@interfaceCupoverFlowException:nSException@ndcupoveException.m#import'cupo VerflowException.h'@ImplementationCupoverflowexception@Endcup。 h# import@interfaceCup:NSObject{intlevel;}-(int)level;-(void)setLevel:(int)l;-(void)fill;-(void)empty;-(void)print;@endCup.m #import 'Cup.h'#import'CupOverflowException.h'#import'CupWarningException.h'#import#import@implementationCup-(id)init{self=[superinit];if(self){[selfsetLevel:0]; }returnself ;}-(int)level{returnlevel;}-(void)setLevel:(int)l{llevel=l;if(level100){//throwoverflowNSException*e=[CupOverflowExceptionExceptionWithName:@'CupOverflowException'原因:@' Thelevelisabove100' userInfo:nil];@throwe;}elseif(level=50){//throwwarningNSException*e=[CupWarningExceptionExceptionWithName:@'CupWarningException'reason:@'Thelevelisaboveorat50'userInfo:nil];@throwe;}elseif(level0) {//throwExceptionNSException*e=[NSExceptionExceptionWithName:@'CupUnderflowException'reason:@'Thelevelisbelow0'userInfo:nil];@throwe;}}-(void)fill{[selfsetLevel:level+10];}-(void)empty {[ selfsetLevel:level-10];}-(void)print{printf('Cuplevelis:%i
',level);}@endmain.m#import'Cup.h'#import'CupOverflowException.h'#import'CupWarningException.h'#import#import#import#importintmain(intargc,constchar*argv[]){NSAutoreleasePool *pool=[[NSAutoreleasePoolalloc]init];Cup*cup=[[Cupalloc]init];inti;//thiswillworkfor(i=0;i4;i++){[cupfill];[cupprint];}//thiswill抛出异常for(i=0;i7;i++){@try{[cupfill];}@catch(CupWarningException*e){printf('%s:',[[ename]cString]);}@catch(CupOverflowException*e){printf ('%s:',[[ename]cString]);}@finally{[cupprint];}}//抛出GenericException@try{[cupsetLevel:-1];}@catch(NSException*e){printf(' %s:%s
',[[ename]cString],[[ereason]cString]);}//freememory[cuprelease];[
poolrelease];} outputCuplevelis:10Cuplevelis:20Cuplevelis:30Cuplevelis:40CupWarningException:Cuplevelis:50CupWarningException:Cuplevelis:60CupWarningException:Cuplevelis:70CupWarningException:Cuplevelis:80CupWarningException:Cuplevelis:90CupWarningException:Cuplevelis:100CupOverflowException:Cuplevelis:110CupUnderflowException:Thelevelisbelow0 NSAutoreleasePool 是一个记忆体管理类别。现在先别管它是干嘛的。 Exceptions(异常情况)的丢出不需要扩充(extend)NSException 物件,你可简单的用 id 来代表它: @catch ( id e ) { ... } 还有一个 finally 区块,它的行为就像 Java 的异常处理方式,finally 区块的内容保证会被唿叫。 Cup.m 裡的 @"CupOverflowException" 是一个 NSString 常数物件。在 Objective-C 中,@ 符号通常用来代表这是语言的衍生部分。C 语言形式的字串(C string)就像 C/C++ 一样是 "String constant" 的形式,型别为 char *。 #p#副标题#e# 继承、多型(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;@endFraction.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;}@endComplex.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;@endComplex.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);}@endmain.m#import#import"Fraction.h"#import"Complex.h"intmain(intargc,constchar*argv[]){//createanewinstanceFraction*frac=[[Fractionalloc]initWithNumerator:1denominator:10];Complex*comp=[[Complexalloc]initWithReal:10andImaginary:15];idnumber;//printfractionnumber=frac;printf("Thefractionis:");[numberprint];printf(" ");//printcomplexnumber=comp;printf("Thecomplexnumberis:");[numberprint];printf(" ");//freememory[fracrelease];[comprelease];return0;} outputThefractionis:1/10Thecomplexnumberis:10.000000+15.000000i 这种动态连结有显而易见的好处。你不需要知道你唿叫 method 的那个东西是什么型别,如果这个物件对这个讯息有反应,那就会唤起这个 method。这也不会牵涉到一堆繁琐的转型动作,比如在 Java 裡唿叫一个整数物件的 .intValue() 就得先转型,然后才能唿叫这个 method。 #p#副标题#e# 继承(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;@endRectangle.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);}@endSquare.h#import"Rectangle.h"@interfaceSquare:Rectangle-(Square*)initWithSize:(int)s;-(void)setSize:(int)s;-(int)size;@endSquare.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];}@endmain.m#import"Square.h"#import"Rectangle.h"#importintmain(intargc,constchar*argv[]){Rectangle*rec=[[Rectanglealloc]initWithWidth:10height:20];Square*sq=[[Squarealloc]initWithSize:15];//printemprintf("Rectangle:");[recprint];printf(" ");printf("Square:");[sqprint];printf(" ");//updatesquare[sqsetWidth:20];printf("Squareafterchange:");[sqprint];printf(" ");//freememory[recrelease];[sqrelease];return0;} outputRectangle:width=10,height=20Square:width=15,height=15Squareafterchange: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] initWithWidth10 height15],会发生什么事?答案是会产生一个编译器错误。因为 rectangle constructor 回传的型别是 Rectangle*,而不是 Square*,所以这行不通。在某种情况下如果你真想这样用,使用 id 型别会是很好的选择。如果你想使用 parent 的 constructor,只要把 Rectangle* 回传型别改成 id 即可。 #p#副标题#e# 动态识别(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"#importintmain(intargc,constchar*argv[]){Rectangle*rec=[[Rectanglealloc]initWithWidth:10height:20];Square*sq=[[Squarealloc]initWithSize:15];//isMemberOfClass//trueif([sqisMemberOfClass:[Squareclass]]==YES){printf("squareisamemberofsquareclass ");}//falseif([sqisMemberOfClass:[Rectangleclass]]==YES){printf("squareisamemberofrectangleclass ");}//falseif([sqisMemberOfClass:[NSObjectclass]]==YES){printf("squareisamemberofobjectclass ");}//isKindOfClass//trueif([sqisKindOfClass:[Squareclass]]==YES){printf("squareisakindofsquareclass ");}//trueif([sqisKindOfClass:[Rectangleclass]]==YES){printf("squareisakindofrectangleclass ");}//trueif([sqisKindOfClass:[NSObjectclass]]==YES){printf("squareisakindofobjectclass ");}//respondsToSelector//trueif([sqrespondsToSelector:@selector(setSize:)]==YES){printf("squarerespondstosetSize:method ");}//falseif([sqrespondsToSelector:@selector(nonExistant)]==YES){printf("squarerespondstononExistantmethod ");}//trueif([SquarerespondsToSelector:@selector(alloc)]==YES){printf("squareclassrespondstoallocmethod ");}//instancesRespondToSelector//falseif([RectangleinstancesRespondToSelector:@selector(setSize:)]==YES){printf("rectangleinstancerespondstosetSize:method ");}//trueif([SquareinstancesRespondToSelector:@selector(setSize:)]==YES){printf("squareinstancerespondstosetSize:method ");}//freememory[recrelease];[sqrelease];return0;} outputsquareisamemberofsquareclasssquareisakindofsquareclasssquareisakindofrectangleclasssquareisakindofobjectclasssquarerespondstosetSize:methodsquareclassrespondstoallocmethodsquareinstancerespondstosetSize:methodCategories 当你想要为某个 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;@endFractionMath.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]];}@endmain.m#import#import"Fraction.h"#import"FractionMath.h"intmain(intargc,constchar*argv[]){//createanewinstanceFraction*frac1=[[Fractionalloc]initWithNumerator:1denominator:3];Fraction*frac2=[[Fractionalloc]initWithNumerator:2denominator:5];Fraction*frac3=[frac1mul:frac2];//printit[frac1print];printf("*");[frac2print];printf("=");[frac3print];printf(" ");//freememory[frac1release];[frac2release];[frac3release];return0;} output1/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;@endMyClass.m#import"MyClass.h"#import@implementationMyClass-(void)publicMethod{printf("publicmethod ");}@end//privatemethods@interfaceMyClass(Private)-(void)privateMethod;@end@implementationMyClass(Private)-(void)privateMethod{printf("privatemethod ");}@endmain.m#import"MyClass.h"intmain(intargc,constchar*argv[]){MyClass*obj=[[MyClassalloc]init];//thiscompiles[objpublicMethod];//thisthrowserrorswhencompiling//[objprivateMethod];//freememory[objrelease];return0;} outputpublicmethodPosing Posing 有点像 categories,但是不太一样。它允许你扩充一个 class,并且全面性地的扮演(pose)这个 super class。例如:你有一个扩充 NSArray 的 NSArrayChild 物件。如果你让 NSArrayChild 扮演 NSArray,则在你的程式码中所有的 NSArray 都会自动被替代为 NSArrayChild。FractionB.h#import"Fraction.h"@interfaceFractionB:Fraction-(void)print;@endFractionB.m#import"FractionB.h"#import@implementationFractionB-(void)print{printf("(%i/%i)",numerator,denominator);}@endmain.m#import#import"Fraction.h"#import"FractionB.h"intmain(intargc,constchar*argv[]){Fraction*frac=[[Fractionalloc]initWithNumerator:3denominator:10];//printitprintf("Thefractionis:");[fracprint];printf(" ");//makeFractionBposeasFraction[FractionBposeAsClass:[Fractionclass]];Fraction*frac2=[[Fractionalloc]initWithNumerator:3denominator:10];//printitprintf("Thefractionis:");[frac2print];printf(" ");//freememory[fracrelease];[frac2release];return0;} outputThefractionis:3/10Thefractionis:(3/10) 这个程式的输出中,第一个 fraction 会输出 3/10,而第二个会输出 (3/10)。这是 FractionB 中实作的方式。 poseAsClass 这个 method 是 NSObject 的一部份,它允许 subclass 扮演 superclass。 #p#副标题#e# Protocols Objective-C 裡的 Protocol 与 Java 的 interface 或是 C++ 的 purely virtual class 相同。Printing.h@protocolPrinting-(void)print;@endFraction.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;@endFraction.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:numeratordenominator:denominator];}@endComplex.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;@endComplex.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);}@endmain.m#import#import"Fraction.h"#import"Complex.h"intmain(intargc,constchar*argv[]){//createanewinstanceFraction*frac=[[Fractionalloc]initWithNumerator:3denominator:10];Complex*comp=[[Complexalloc]initWithReal:5andImaginary:15];idprintable;idcopyPrintable;//printitprintable=frac;printf("Thefractionis:");[printableprint];printf(" ");//printcomplexprintable=comp;printf("Thecomplexnumberis:");[printableprint];printf(" ");//thiscompilesbecauseFractioncomformstobothPrintingandNSCopyablecopyPrintable=frac;//thisdoesn'tcompilebecauseComplexonlyconformstoPrinting//copyPrintable=comp;//testconformance//trueif([fracconformsToProtocol:@protocol(NSCopying)]==YES){printf("FractionconformstoNSCopying ");}//falseif([compconformsToProtocol:@protocol(NSCopying)]==YES){printf("ComplexconformstoNSCopying ");}//freememory[fracrelease];[comprelease];return0;} outputThefractionis:3/10Thecomplexnumberis:5.000000+15.000000iFractionconformstoNSCopying 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 )]。 #p#副标题#e# 记忆体管理 到目前为止我都刻意避开 Objective-C 的记忆体管理议题。你可以唿叫物件上的 dealloc,但是若物件裡包含其他物件的指标的话,要怎么办呢?要释放那些物件所佔据的记忆体也是一个必须关注的问题。当你使用 Foundation framework 建立 classes 时,它如何管理记忆体?这些稍后我们都会解释。 注意:之前所有的範例都有正确的记忆体管理,以免你混淆。 Retain and Release(保留与释放) Retain 以及 release 是两个继承自 NSObject 的物件都会有的 methods。每个物件都有一个内部计数器,可以用来追踪物件的 reference 个数。如果物件有 3 个 reference 时,不需要 dealloc 自己。但是如果计数器值到达 0 时,物件就得 dealloc 自己。[object retain] 会将计数器值加 1(值从 1 开始),[object release] 则将计数器值减 1。如果唿叫 [object release] 导致计数器到达 0,就会自动 dealloc。Fraction.m...-(void)dealloc{printf("Deallocingfraction ");[superdealloc];}...main.m#import"Fraction.h"#importintmain(intargc,constchar*argv[]){Fraction*frac1=[[Fractionalloc]init];Fraction*frac2=[[Fractionalloc]init];//printcurrentcountsprintf("Fraction1retaincount:%i ",[frac1retainCount]);printf("Fraction2retaincount:%i ",[frac2retainCount]);//incrementthem[frac1retain];//2[frac1retain];//3[frac2retain];//2//printcurrentcountsprintf("Fraction1retaincount:%i ",[frac1retainCount]);printf("Fraction2retaincount:%i ",[frac2retainCount]);//decrement[frac1release];//2[frac2release];//1//printcurrentcountsprintf("Fraction1retaincount:%i ",[frac1retainCount]);printf("Fraction2retaincount:%i ",[frac2retainCount]);//releasethemuntiltheydeallocthemselves[frac1release];//1[frac1release];//0[frac2release];//0} outputFraction1retaincount:1Fraction2retaincount:1Fraction1retaincount:3Fraction2retaincount:2Fraction1retaincount:2Fraction2retaincount:1DeallocingfractionDeallocingfraction Retain call 增加计数器值,而 release call 减少它。你可以唿叫 [obj retainCount] 来取得计数器的 int 值。 当当 retainCount 到达 0,两个物件都会 dealloc 自己,所以可以看到印出了两个 "Deallocing fraction"。 Dealloc 当你的物件包含其他物件时,就得在 dealloc 自己时释放它们。Objective-C 的一个优点是你可以传递讯息给 nil,所以不需要经过一堆防错测试来释放一个物件。 AddressCard.h#import#import@interfaceAddressCard:NSObject{NSString*first;NSString*last;NSString*email;}-(AddressCard*)initWithFirst:(NSString*)flast:(NSString*)lemail:(NSString*)e;-(NSString*)first;-(NSString*)last;-(NSString*)email;-(void)setFirst:(NSString*)f;-(void)setLast:(NSString*)l;-(void)setEmail:(NSString*)e;-(void)setFirst:(NSString*)flast:(NSString*)lemail:(NSString*)e;-(void)setFirst:(NSString*)flast:(NSString*)l;-(void)print;@endAddressCard.m#import"AddressCard.h"#import@implementationAddressCard-(AddressCard*)initWithFirst:(NSString*)flast:(NSString*)lemail:(NSString*)e{self=[superinit];if(self){[selfsetFirst:flast:lemail:e];}returnself;}-(NSString*)first{returnfirst;}-(NSString*)last{returnlast;}-(NSString*)email{returnemail;}-(void)setFirst:(NSString*)f{[fretain];[firstrelease];ffirst=f;}-(void)setLast:(NSString*)l{[lretain];[lastrelease];llast=l;}-(void)setEmail:(NSString*)e{[eretain];[emailrelease];eemail=e;}-(void)setFirst:(NSString*)flast:(NSString*)lemail:(NSString*)e{[selfsetFirst:f];[selfsetLast:l];[selfsetEmail:e];}-(void)setFirst:(NSString*)flast:(NSString*)l{[selfsetFirst:f];[selfsetLast:l];}-(void)print{printf("%s%s<%S>",[firstcString],[lastcString],[emailcString]);}-(void)dealloc{[firstrelease];[lastrelease];[emailrelease];[superdealloc];}@endmain.m#import"AddressCard.h"#import#importintmain(intargc,constchar*argv[]){NSString*first=[[NSStringalloc]initWithCString:"Tom"];NSString*last=[[NSStringalloc]initWithCString:"Jones"];NSString*email=[[NSStringalloc]initWithCString:"tom@"];AddressCard*tom=[[AddressCardalloc]initWithFirst:firstlast:lastemail:email];//we'redonewiththestrings,sowemustdeallocthem[firstrelease];[lastrelease];[emailrelease];//printtoshowtheretaincountprintf("Retaincount:%i ",[[tomfirst]retainCount]);[tomprint];printf(" ");//freememory[tomrelease];return0;} outputRetaincount:1TomJones 如 AddressCard.m,这个範例不仅展示如何撰写一个 dealloc method,也展示了如何 dealloc 成员变数。 每个 set method 裡的叁个动作的顺序非常重要。假设你把自己当参数传给一个自己的 method(有点怪,不过确实可能发生)。若你先 release,「然后」才 retain,你会把自己给解构(destruct,相对于建构)!这就是为什么应该要 1) retain 2) release 3) 设值 的塬因。 通常我们不会用 C 形式字串来初始化一个变数,因为它不支援 unicode。下一个 NSAutoreleasePool 的例子会用展示正确使用并初始化字串的方式。 这只是处理成员变数记忆体管理的一种方式,另一种方式是在你的 set methods 裡面建立一份拷贝。 Autorelease Pool 当你想用 NSString 或其他 Foundation framework classes 来做更多程式设计工作时,你需要一个更有弹性的系统,也就是使用 Autorelease pools。 当开发 Mac Cocoa 应用程式时,autorelease pool 会自动地帮你设定好。main.m#import#import#importintmain(intargc,constchar*argv[]){NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];NSString*str1=@"constantstring";NSString*str2=[NSStringstringWithString:@"stringmanagedbythepool"];NSString*str3=[[NSStringalloc]initWithString:@"selfmanagedstring"];//printthestringsprintf("%sretaincount:%x ",[str1cString],[str1retainCount]);printf("%sretaincount:%x ",[str2cString],[str2retainCount]);printf("%sretaincount:%x ",[str3cString],[str3retainCount]);//freememory[str3release];//freepool[poolrelease];return0;} outputconstantstringretaincount:ffffffffstringmanagedbythepoolretaincount:1selfmanagedstringretaincount:1 如果你执行这个程式,你会发现几件事:第一件事,str1 的 retainCount 为 ffffffff。 另一件事,虽然我只有 release str3,整个程式却还是处于完美的记忆体管理下,塬因是第一个常数字串已经自动被加到 autorelease pool 裡了。还有一件事,字串是由 stringWithString 产生的。这个 method 会产生一个 NSString class 型别的字串,并自动加进 autorelease pool。 千万记得,要有良好的记忆体管理,像 [NSString stringWithString@"String"] 这种 method 使用了 autorelease pool,而 alloc method 如 [[NSString alloc] initWithString@"String"] 则没有使用 auto release pool。 在 Objective-C 有两种管理记忆体的方法, 1) retain and release or 2) retain and release/autorelease。 对于每个 retain,一定要对应一个 release 「或」一个 autorelease。 下一个範例会展示我说的这点。Fraction.h...+(Fraction*)fractionWithNumerator:(int)ndenominator:(int)d;...Fraction.m...+(Fraction*)fractionWithNumerator:(int)ndenominator:(int)d{Fraction*ret=[[Fractionalloc]initWithNumerator:ndenominator:d];[retautorelease];returnret;}...main.m#import#import"Fraction.h"#importintmain(intargc,constchar*argv[]){NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];Fraction*frac1=[FractionfractionWithNumerator:2denominator:5];Fraction*frac2=[FractionfractionWithNumerator:1denominator:3];//printfrac1printf("Fraction1:");[frac1print];printf(" ");//printfrac2printf("Fraction2:");[frac2print];printf(" ");//thiscausesasegmentationfault//[frac1release];//releasethepoolandallobjectsinit[poolrelease];return0;} outputFraction1:2/5Fraction2:1/3 在这个例子裡,此 method 是一个 class level method。在物件建立后,在它上面唿叫 了 autorelease。在 main method 裡面,我从未在此物件上唿叫 release。 这样行得通的塬因是:对任何 retain 而言,一定要唿叫一个 release 或 autorelease。物件的 retainCount 从 1 起跳 ,然后我在上面唿叫 1 次 autorelease,表示 1 - 1 = 0。当 autorelease pool 被释放时,它会计算所有物件上的 autorelease 唿叫次数,并且唿叫相同次数的 [obj release]。 如同註解所说,不把那一行註解掉会造成分段错误(segment fault)。因为物件上已经唿叫过 autorelease,若再唿叫 release,在释放 autorelease pool 时会试图唿叫一个 nil 物件上的 dealloc,但这是不允许的。最后的算式会变为:1 (creation) - 1 (release) - 1 (autorelease) = -1 管理大量暂时物件时,autorelease pool 可以被动态地产生。你需要做的只是建立一个 pool,执行一堆会建立大量动态物件的程式码,然后释放这个 pool。你可能会感到好奇,这表示可能同时有超过一个 autorelease pool 存在。 #p#副标题#e# Foundation framework classes Foundation framework 地位如同 C++ 的 Standard Template Library。不过 Objective-C 是真正的动态识别语言(dynamic types),所以不需要像 C++ 那样肥得可怕的样版(templates)。这个 framework 包含了物件组、网路、执行绪,还有更多好东西。 NSArraymain.m#import#import#import#import#importvoidprint(NSArray*array){NSEnumerator*enumerator=[arrayobjectEnumerator];idobj;while(obj=[enumeratornextObject]){printf("%s ",[[objdescription]cString]);}}intmain(intargc,constchar*argv[]){NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];NSArray*arr=[[NSArrayalloc]initWithObjects:@"Me",@"Myself",@"I",nil];NSMutableArray*mutable=[[NSMutableArrayalloc]init];//enumerateoveritemsprintf("----staticarray ");print(arr);//addstuff[mutableaddObject:@"One"];[mutableaddObject:@"Two"];[mutableaddObjectsFromArray:arr];[mutableaddObject:@"Three"];//printemprintf("----mutablearray ");print(mutable);//sortthenprintprintf("----sortedmutablearray ");[mutablesortUsingSelector:@selector(caseInsensitiveCompare:)];print(mutable);//freememory[arrrelease];[mutablerelease];[poolrelease];return0;} output----staticarrayMeMyselfI----mutablearrayOneTwoMeMyselfIThree----sortedmutablearrayIMeMyselfOneThreeTwo 阵列有两种(通常是 Foundation classes 中最资料导向的部分),NSArray 跟 NSMutableArray,顾名思义,mutable(善变的)表示可以被改变,而 NSArray 则不行。这表示你可以製造一个 NSArray 但却不能改变它的长度。 你可以用 Obj, Obj, Obj, ..., nil 为参数唿叫建构子来初始化一个阵列,其中 nil 表示结尾符号。 排序(sorting)展示如何用 selector 来排序一个物件,这个 selector 告诉阵列用 NSString 的忽略大小写顺序来排序。如果你的物件有好几个排序方法,你可以使用这个 selector 来选择你想用的方法。 在 print method 裡,我使用了 description method。它就像 Java 的 toString,会回传物件的 NSString 表示法。 NSEnumerator 很像 Java 的列举系统。while ( obj = [array objectEnumerator] ) 行得通的理由是 objectEnumerator 会回传最后一个物件的 nil。在 C 裡 nil 通常代表 0,也就是 false。改用 ( ( obj = [array objectEnumerator] ) != nil ) 也许更好。 NSDictionarymain.m#import#import#import#import#import<<>#importvoidprint(NSDictionary*map){NSEnumerator*enumerator=[mapkeyEnumerator];idkey;while(key=[enumeratornextObject]){printf("%s=>%s ",[[keydescription]cString],[[[mapobjectForKey:key]description]cString]);}}intmain(intargc,constchar*argv[]){NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];NSDictionary*dictionary=[[NSDictionaryalloc]initWithObjectsAndKeys:@"one",[NSNumbernumberWithInt:1],@"two",[NSNumbernumberWithInt:2],@"three",[NSNumbernumberWithInt:3],nil];NSMutableDictionary*mutable=[[NSMutableDictionaryalloc]init];//printdictionaryprintf("----staticdictionary ");print(dictionary);//addobjects[mutablesetObject:@"Tom"forKey:@"tom@"];[mutablesetObject:@"Bob"forKey:@"bob@"];//printmutabledictionaryprintf("----mutabledictionary ");print(mutable);//freememory[dictionaryrelease];[mutablerelease];[poolrelease];return0;} output ----staticdictionary1=>one2=>two3=>three----mutabledictionarybob@=>Bobtom@=>Tom 优点与缺点 优点 Cateogies Posing 动态识别 指标计算 弹性讯息传递 不是一个过度复杂的 C 衍生语言 可透过 Objective-C++ 与 C++ 结合 缺点 不支援命名空间 不支援运算子多载(虽然这常常被视为一个优点,不过 正确地使用运算子多载可以降低程式码复杂度) 语言裡仍然有些讨厌的东西,不过不比 C++ 多。 #p#副标题#e#