2010年11月编程语言排行榜和2月编程语言排行榜都是关于Objective-C的。Objective-C是Mac软件开发领域最重要的开发语言。如果熟悉面向对象的思想和C语言,对我们学习Objective-C会有很大的帮助。如果我们对C语言不熟悉,那么我们就需要学习C语言。调用方法(CallingMethods)为了尽快上手,我们先来看一些简单的例子。Objective-C语法中的基本方法调用是这样的:[objectmethod];[objectmethodWithInput:输入];对象的方法可以返回一个值:output=[objectmethodWithOutput];输出=[objectmethodWithInputAndOutput:输入];我们也可以在类中调用如何创建对象的方法。在下面的例子中,我们调用了NSString类的string方法:idmyObject=[NSStringstring];id的类型意味着变量myObject可以指向任何类型的变量。当我们编译这个应用程序时,我们并不知道它实现的真正的类和方法。在这个例子中,很明显这个对象的类型应该是NSString,所以我们可以改变他的类型:NSString*myString=[NSStringstring];现在myString是一个NSString类型的变量。这时候,如果我们尝试使用NSString没有实现的方法,编译器就会警告我们。请务必注意对象类型右侧有一个星号。所有Objective-C对象变量都是指针类型。id类型已被预定义为指针类型。所以我们不需要添加星号。嵌套消息在许多编程语言中,嵌套消息或嵌套函数看起来像这样:function1(function2());function2的返回值作为输入参数传递给function1。在Objective-C中,嵌套消息调用如下所示:[NSStringstringWithFormat:[prefsformat]];我们应该尽量避免在一行代码中嵌套两个以上的调用。因为在这种情况下,代码的可读性不是很好。#p#多输入法(Multi-InputMethods)多输入参数的方法。在Objective-C中,一个方法名可以分成几个部分。在头文件中,您应该定义一个具有多个输入参数的方法,如下所示:-(BOOL)writeToFile:(NSString*)pathatomically:(BOOL)useAuxiliaryFile;我们这样称呼它:BOOLresult=[myDatawriteToFile:@"/tmp/log.txt"atomically:NO];该参数不必命名。该方法在运行时系统中的真实名称是writeToFile:atomically:。访问器(Getter&Setter)Objective-C中的所有实例对象默认都是私有的。所以在大多数情况下我们需要使用访问器来读取或设置变量的值。有两种语法支持这样的操作,这是传统的老语法:[photosetCaption:@"DayattheBeach"];输出=[图片说明];第二行代码其实并不是直接读取对象实例的变量。实际上它调用了一个名为caption的方法。在大多数情况下,在Objective-C中我们不需要在getter前面加上get前缀。每当我们看到方括号时,实际上是在向对象或类发送消息。#p#Dot语法在Objective-C2.0中,一个新的“.”添加了操作语法。在MacOSX10.5中,使用Objective-C2.0语法:photo.caption=@"DayattheBeach";输出=照片.标题;我们可以使用这两种方法。但是最好在一个项目中保持风格一致,只使用某一种风格。这”。”operation只能用在setters和getters中,不能用在一般方法中。创建对象创建对象有两种主要方法。第一种方法是这样的:NSString*myString=[NSStringstring];这是一种非常地道的风格。在这种情况下,我们创建的是系统自动释放类型的对象。关于自动释放类型autoreleased,后面会深入讨论。然而,在很多情况下,我们需要手动创建对象:NSString*myString=[[NSStringalloc]init];这是一个嵌套的方法调用。第一个调用NSString自己的alloc方法。这是一个相对低级的调用,因为它创建内容并实例化一个对象。第二个代码块调用新创建的对象的init方法。这个init方法实现了比较常用的基础设置,比如创建实例对象的参数。普通开发人员不清楚实现此客户端类的细节。在某些情况下,我们可以使用不同的初始化方法来赋初值:NSNumber*value=[[NSNumberalloc]initWithFloat:1.0];基本内存管理如果我们正在为MacOSX开发应用程序,我们可以选择是否启用垃圾回收。这意味着我们不需要考虑内存管理,除非我们需要处理一个特别复杂的情况。但是有的时候我们的开发环境是没有垃圾回收机制的,比如开发iPhone的时候就没有垃圾回收机制。在这种情况下,我们需要了解一些基本的内存管理概念。如果我们通过alloc手动创建一个对象,我们需要在使用完对象后释放它。我们不需要手动释放一个自动释放类型的对象。如果我们这样做,我们的应用程序将崩溃。这里有两个例子://string1willbereleasedautomaticallyNSString*string1=[NSStringstring];//mustreleasethiswhendoneNSString*string2=[[NSStringalloc]init];[string2release];方法调用完成后释放。当然,内存管理还有很多东西是我们刚需学习的,但这需要我们了解更多的基本概念,才能涉足。#p#设计一个类接口对于Objective-C语言来说,创建一个类是非常简单的。它通常分为两部分。类接口通常存放在ClassName.h文件中,里面定义了实例的参数,以及一些公共方法。该类的实现在ClassName.m文件中。它包含代码和那些实际工作的方法。它还经常定义一些私有方法。这些私有方法对子类不可见。这是接口文件的概要。类名是Photo,所以文件名是Photo.h:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}@end首先,我们导入Cocoa.h。Cocoa应用程序中的大多数基本类都是这样做的。#import宏会自动避免多次包含同一个文件。@interface符号表示这是Photo类的声明。冒号指定父类。上面例子的父类是NSObject。在大括号内,有两个变量:标题和摄影师。两者都是NSString类型。当然,它们也可以是任何其他类型,包括id类型。最后@end结束整个语句。添加方法让我们为成员变量添加一些getter:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}-标题;-摄影师;@end不要忘记,Objective-C方法不需要以get为前缀。单条表示它是一个实例方法。如果是加号,则表示是类的方法。编译器默认的方法返回类型是id。另外,所有方法的默认参数类型也是id类型。所以上面的代码在技术上是正确的。但这很少使用。让我们为它添加返回类型:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}-(NSString*)标题;-(NSString*)摄影师;@end接下来我们添加设置器:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}-(NSString*)标题;-(NSString*)摄影师;-(void)setCaption:(NSString*)输入;-(void)setPhotographer:(NSString*)输入;@endSetters不需要返回任何值,所以我们指定它的类型为void。#p#Classimplementation我们通过实现getters创建一个类实现:#import"Photo.h"@implementationPhoto-(NSString*)caption{返回字幕;}-(NSString*)photographer{returnphotographer;}@end这部分代码以@implementation开头,后跟类名,以@end结尾。就像一个类的接口定义一样,所有的方法都和接口定义中的一样。所有对象都必须被定义和实现。如果我们以前编写过代码,那么Objective-C中的getter看起来就像其他任何东西。那么下面就来介绍一下setter,需要稍微解释一下。-(void)setCaption:(NSString*)input{[captionautorelease];标题=[输入保留];}-(void)setPhotographer:(NSString*)input{[photographerautorelease];摄影师=[inputretain];每个setter处理两个变量。首先是应用当前存在的对象。第二个是新的输入对象。在支持垃圾回收的开发环境中,我们只需要直接赋一个新值即可:-(void)setCaption:(NSString*)input{caption=input;但是如果不能使用垃圾回收机制,我们需要先保留旧对象,再保留新对象。有两种释放引用对象的方法:release和autorelease。标准版本将直接删除引用。autorelease方法将在将来的某个时间释放它。直到其生命周期结束,它将毫无疑问地存在。在这个例子中,上面setPhotographer中的photographer对象将在函数结束时被释放。在setter中使用autorelease是安全的,因为新对象和旧对象可能是同一个对象,也可能指向同一个对象。对于我们即将保留的对象,我们不应该立即释放它。这在现在看来可能令人困惑,但随着我们学习,我们会越来越理解它。现在我们不需要马上完全理解它。#p#Initialization我们可以创建一个初始化方法,为类实例的成员变量赋初值:-(id)init{if(self=[superinit]){[selfsetCaption:@"DefaultCaption"];[selfsetPhotographer:@"DefaultPhotographer"];返回自我;}上面的代码感觉没什么好解释的,虽然第二行代码好像没什么用。这是一个单等号,就是把[superinit]的值赋给self。它基本上是调用父类来进行初始化。这个if代码段是为了在设置默认值之前验证初始化是否成功。释放资源Deallocdealloc方法在对象希望从其内容中删除时被调用。这是我们释放子类中引用的成员变量的最佳时机:-(void)dealloc{[captionrelease];[摄影师发布];[超级分配];在前两行中,我们将发布通知发送给两个成员变量。我们这里不使用自动释放。使用标准版本更快。最后一行[superdealloc];非常重要。我们要发个消息让父类自己清除。如果不这样做,则对象还没有清理干净,存在内存泄漏。dealloc在垃圾回收机制下是不会被调用的。相反,我们需要实现finalize方法。#p#关于内存管理的更多信息Objective-C的内存管理系统是基于引用计数的。我们需要关心的只是跟踪我们的引用以及内存是否在运行时实际释放。用最简单的话来说,当我们分配一个对象时,它应该在某个时候被保留。每次我们调用alloc或retain时,我们都必须调用release。这就是引用计数理论。但在实践中,只有两种情况需要创建对象:1.作为类的成员变量2.仅在函数中临时使用,更多情况下,成员变量的setter应该只是自动释放对象,然后保留新对象。我们只需要在dealloc的时候调用release。所以真正需要做的就是管理函数内部对本地的引用。唯一的原则是:如果我们分配或复制一个对象,那么我们需要在函数结束时释放或自动释放它。如果我们以另一种方式创建它,那没关系。下面是一个管理成员对象的例子:-(void)setTotalAmount:(NSNumber*)input{[totalAmountautorelease];totalAmount=[inputretain];}-(void)dealloc{[totalAmountrelease];[超级分配];}这里是一个本地的参考例子。我们只需要释放我们用alloc创建的对象:NSNumber*value1=[[NSNumberalloc]initWithFloat:8.75];NSNumber*value2=[NSNumbernumberWithFloat:14.78];//只释放值1,不释放值2[value1release];设置成员变量的例子:NSNumber*value1=[[NSNumberalloc]initWithFloat:8.75];[selfsetTotal:value1];NSNumber*value2=[NSNumbernumberWithFloat:14.78];[selfsetTotal:value2];[价值1释放];注意如何管理本地引用实际上都是一样的。不管你是否将它设置为成员变量。我们不必担心setter的内部实现。如果我们理解了这些,我们基本上就理解了80%的Objective-C内存管理。#p#AttributeProperties前面我们写caption和author访问器的时候,你可能已经注意到代码非常简洁,应该是抽象的。属性是Objective-C中的一个新特性。他让我们可以自动生成accessor,还有一些其他的好处。我们可以将上面的Photo类转化为一个属性:上面类的原实现如下:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}-(NSString*)标题;-(NSString*)摄影师;-(void)setCaption:(NSString*)输入;-(void)setPhotographer:(NSString*)输入;@end如果是用属性实现的:#import@interfacePhoto:NSObject{NSString*caption;NSString*摄影师;}@property(retain)NSString*caption;@property(retain)NSString*photographer;@end@property是用于声明属性的Objective-C编译指令。括号中的“retain”表示setter需要保留输入对象。该行的其余部分指定属性的类型和名称。我们来看看这个类的实现:#import"Photo.h"@implementationPhoto@synthesizecaption;@synthesizephotographer;-(void)dealloc{[captionrelease];[摄影师发布];[超级分配];}@end@synthesize指令自动生成我们的setter和getter。所以我们只需要实现类的dealloc方法即可。只有访问器不存在时才会生成访问器。所以你可以放心使用@synthesize来指定属性。并随意实施您自己的getter和setter。编译器会自己找出缺少的方法。属性声明还有其他选项,但限于篇幅,我们下次再介绍。登录Objective-C,向控制台写入日志非常简单。其实NSLog()和C语言的printf()函数几乎是一模一样的,只不过NSLog多了一个“%@”来获取对象。NSLog(@"当前日期和时间是:%@",[NSDatedate]);我们可以将一个对象记录到控制台。NSLog函数调用要输出对象的描述方法,然后打印返回的NSString。我们可以重写我们自己类中的描述方法,这样我们就可以得到一个自定义的字符串。#p#在Nil上调用方法在Objective-C中,nil对象被设计为与NULL指针相关联。它们的区别在于nil是一个对象,而NULL只是一个值。并且我们不会为调用该方法的nil产生崩溃或抛出异常。该技术被框架以多种不同的方式使用。最主要的是我们现在不必在调用方法之前检查对象是否为nil。如果我们调用一个返回nil对象的方法,那么我们将得到一个nil返回值。我们可以使用nil对象使我们的dealloc函数实现看起来更好:-(void)dealloc{self.caption=nil;self.photographer=nil;[超级分配];}这是可能的,因为我们给nil对象设置了一个成员变量,setter会保留nil对象(当然这时候nil对象什么也不会做),然后释放旧对象。这种释放对象的方式其实更好,因为这样一来,成员变量连指向随机数据的机会都没有,而通过其他方式,指向随机数据的情况是不可避免的。请注意我们称为self.VAR的语法,这意味着我们正在使用setter并且不会导致任何内存问题。如果我们直接设置值,就会出现内存溢出://incorrect.causesamemoryleak。//useself.captiontogothroughsettercaption=nil;CategoriesCategories是Objective-C中最常用的函数之一。基本上,类别允许我们在不添加子类的情况下向现有类添加方法。而且你不需要知道它的内部实现。如果我们想将方法添加到框架自带的类中,这将非常有用。如果我们想给我们程序项目的NSString添加一个方法,我们可以使用category。您甚至不需要自己实现NSString的子类。比如我们想给NSString增加一个判断是否是URL的方法,那么我们可以这样做:#import@interfaceNSString(Utilities)-(BOOL)isURL;@end这与类的定义非常相似。不同的是category没有父类,必须有括号中的category名称。名字可以随意取,但是习惯的名字会让人理解类中的函数和方法。下面是具体实现。但是请注意,这对于判断URL本身并不是一个很好的实现。我们主要是想从整体上理解类别的概念。#import"NSString-Utilities.h"@implementationNSString(Utilities)-(BOOL)isURL{if([selfhasPrefix:@"http://"])returnYES;否则返回NO;}@end现在我们可以使用任何NSString类这个方法可以在任何对象上调用。以下代码在控制台打印“string1isaURL”:NSString*string1=@"http://www.CocoaDev.cn/";NSString*string2=@"皮克斯";如果([string1isURL])NSLog(@"string1isaURL");如果([string2isURL])NSLog(@"string2isaURL");与子类不同,类别不能添加成员变量。我们也可以使用category来覆盖类已有的方法,但是这个需要非常非常小心。请记住,当我们按类别修改一个类时,它会影响应用程序中该类的所有对象。后记上面的Objective-C比较基础,粗略的讲了一遍。Objective-C还是比较好用的。没有特别的语法需要学习。并且一些概念在Objective-C中被反复使用。
