初学者必学文档:Objective-C语法入门

Objective-C语法入门是本文要介绍的内容,Objective-C是Mac软件开发领域最主要的开发语言。假如我们对面向对象的思维已经C语言都很熟悉的话,对于我们学习Objective-C将会非常有用。假如我们对C语言还不熟悉的话,那我们需要学习一下C语言。

方法调用(Calling Methods)

为了能够尽快上手,我们先来看一些简单的例子。Objective-C语法里面基本的方法调用是这样的:

程序代码:

[object method];  


[object methodWithInput:input]; 

对象的方法可以返回值:

程序代码:

output = [object methodWithOutput];  



output = [object methodWithInputAndOutput:input]; 

我们也可以在类里面调用如何创建对象的方法。下面的这个例子里面,我们调用了NSString类的string方法:

id myObject = [NSString string]; 

id的类型意味着myObject这个变量可以指向任意类型的变量。当我们编译这个应用程序的时候,并不知道他实现的真实的类和方法。

在这个例子里面,很明显这个对象的类型应该是NSString,所以我们可以改一下他的类型 

NSString* myString = [NSString string]; 

现在myString就是一个NSString类型的变量。这个时候假如我们试图使用一个NSString没有实现的方法时,编译器就会警告我们。

一定要注意在对象类型的右边有一个星号。所有的Objective-C对象变量都是指针类型的。id类型已经预先被定义成一个指针类型了。所以我们不需要再加星号。

嵌套消息调用(Nested Messages)

在许多编程语言里面嵌套消息,或者嵌套函数看起来就像这样:

function1 ( function2() ); 

function2的返回值被传递给function1当输入参数。在Objective-C里面,嵌套消息调用就像这样:

[NSString stringWithFormat:[prefs format]]; 

我们应该尽量避免在一行代码里面嵌套调用超过两个。因为这样的话,代码的可读性就不太好。

多参输入的方法(Multi-Input Methods)

多个输入参数的方法。在Objective-C里面,一个方法名可以被分割成几段。在头文件里面,就应该这样子来定义一个多输入参数的方法:

-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; 

我们这样来调用它:

BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO]; 

参数不一定要给它命名。在运行期系统里面这个方法真实的名字叫writeToFile:atomically:。
 

Accessors(Getter & Setter) 

在Objective-C里面所有的实例对象默认都是私有的。所有在大多数情况下我们需要用accessors去读取或者设置变量的值。有两个语法都支持这样的操作,这个时传统的老的语法:

[photo setCaption:@"Day at the Beach"];  



output = [photo caption]; 

第二行的代码其实并非直接去读对象实例的变量。事实上它调用的是名叫caption的方法。在Objective-C里大多数情况下我们不需要给getters加get的前缀。

无论什么时候我们见到方括号,其实我们都是向一个对象或者一个类发送了一个消息。

Dot Syntax 

在bjective-C 2.0里面,新增加了一个"."操作的语法。在Mac OS X 10.5里面就使用了Objective-C 2.0语法:

photo.caption = @"Day at the Beach";  



output = photo.caption; 

我们两种方式都可以使用。但是在一个工程里面最好保持风格一致,只使用某一种。"."操作只能够被使用在setters和getters里面,而不能用在一般意思的方法上。

创建对象

主要有两种方式来创建一个对象。第一种办法像这面这样:

NSString* myString = [NSString string]; 

这是一种非常习惯性的风格。在这种方式情况下,我们创建的是系统自动释放(autoreleased)类型的对象。关于自动释放类型autoreleased,我们以后会深入讨论一下。然而在许多情况下,我们需要手动的去创建对象:

NSString* myString = [[NSString alloc] init]; 

这是一个嵌套的方法调用。第一个调用的NSString自己的alloc方法。这是一个相对比较底层的调用,因为他创建了内容,以及实例化了一个对象。

第二块代码调用了新创建对象的init方法。这个init方法实现了比较常用的基本设置,比如创建实例对象的参数。对于一般开发人员而言,实现这个客户的类的具体的细节并不清楚。

在一些情况下,我们可以用不通的初始化方式去赋初值:

NSNumber* value = [[NSNumber alloc] initWithFloat:1.0]; 

基本的内存管理

假如我们正在为Mac OS X开发一个应用程序,我们可以选择是否启用垃圾回收机制。这就意味着我们不需要去考虑内存管理,除了一个特别复杂的情形我们需要处理一下。

然而,我们有的时候我们的开发环境没有垃圾回收机制,比如iPhone开发的时候就没有垃圾回收机制。在这种情况下,我们就需要了解一些基本的内存管理方面的概念。

假如我们手动的通过alloc创建了一个对象,我们需要用完这个对象后release它。我们不需要手动的去release一个autoreleased类型的对象,假如真的这样去做的话,我们的应用程序将会crash。

这里有两个例子:

程序代码:

// string1 will be released automatically  



NSString* string1 = [NSString string];  



// must release this when done  



NSString* string2 = [[NSString alloc] init];  



[string2 release]; 

就这个教程而言,我们可以人为autoreleased对象会在当前函数方法调用完成后被释放。

当然了,还有很多关于内存管理的只是我们需要学习,但是这需要我们了解更多的基本概念以后才能去涉及。

设计一个类的Interface

就Objective-C语言而言,创建一个类非常简单。它非常典型的分成了两个部分。

类的接口通常保存在ClassName.h文件里,它定义了实例的参数,以及一些公开的方法。

类的实现在ClassName.m文件里。它包含了真正运行的代码和那些方法。它还经常定义一些私有的方法。这些私有的方法对于子类是不可见的。

这里有一个接口文件的大概。类名Photo,所以文件名叫Photo.h:

程序代码:

#import  


@interface Photo : NSObject {  


NSString* caption;  


NSString* photographer;  


}  


@end 

首先,我们把Cocoa.h import进来。Cocoa的应用程序的所有的基本的类大多都是这样做的。#import宏指令会自动的避免把同一个文件包含多次。

@interface符号表明这是Photo类的声明。冒号指定了父类。上面这个例子父类就是NSObject。

在大括弧里面,有两个变量:caption和photographer。两个都是NSString类型的。当然了,他们也可以是任何别的类型包括id类型的。

最后@end结束整个声明。

增加方法

让我们为成员变量加一些getters:

程序代码:

#import  


@interface Photo : NSObject {  


NSString* caption;  


NSString* photographer;  


}  


- caption;  


- photographer;  


@end 

别忘记,Objective-C方法不需要加get前缀。一个单独小横杆表明它是一个实例的方法。假如是一个加号的话,那就说明它是一个类的方法。

编译器默认的方法的返回类型为id。还有所有的方法的参数的默认类型也都是id类型的。所以上面的代码从技术上讲是对的。但是很少这么用。我们还是给它加上返回类型吧:

程序代码:

#import  


@interface Photo : NSObject {  


NSString* caption;  


NSString* photographer;  


}  


- (NSString*) caption;  


- (NSString*) photographer;  


@end 

下面我们再加上setters:

程序代码:

#import  


@interface Photo : NSObject {  


NSString* caption;  


NSString* photographer;  


}  


- (NSString*) caption;  


- (NSString*) photographer;  


- (void) setCaption: (NSString*)input;  


- (void) setPhotographer: (NSString*)input;  


@end 

Setters不需要返回任何值,所以我们把它的类型指定为void.

类的实现

我们通过实现getters来创建一个类的实现:

程序代码:

    #import "Photo.h"  


@implementation Photo  


- (NSString*) caption {  


return caption;  


}  


- (NSString*) photographer {  


return photographer;  


}  


@end 

这部分的代码由@implementation再来加上类名开始,以@end结束。就跟类的接口定义一样,所有的方法跟接口定义里的一样。所有的对象都必要既要定义也要实现。

假如我们以前也写过代码的话,Objective-C里面的getters看上去跟别的差不多。所以我们下面就来介绍setters,它需要一点说明。

程序代码:

- (void) setCaption: (NSString*)input  


{  


[caption autorelease];  



caption = [input retain];  



}  


- (void) setPhotographer: (NSString*)input  


{  


[photographer autorelease];  



photographer = [input retain];  



} 

每个setter处理两个变量。第一个是当前存在对象的应用。第二个是新的输入对象。在支持垃圾回收的开发环境里,我们只要直接赋新值就可以了:

程序代码:

- (void) setCaption: (NSString*)input {  



caption = input;  



} 

但是假如我们不可以用垃圾回收机制的话,我们就需要先retain旧的对象,然后retain新的对象。

有两种方法可以释放一个引用对象:release 和 autorelease。标准的release会直接删除引用。autorelease方法会在将来的某个时候去release它。在它声明周期结束前,它会毫无疑问的存在。在本例中,上面setPhotographer中的photographer对象,将会在函数结束的时候被释放。

在setter里面用autorelease是安全的,因为新对象跟老的对象有可能是同一个对象有可能指向的是同一个对象。对于一个我们即将retain的对象,我们不应该立即release它。

这个也许现在看起来会困惑,但是随着我们的学习,会越来越能理解它。现在我们不需要立刻完全理解它。

初始化

我们可以创建一个初始化方法去给类的实例的成员变量赋初值:

程序代码:

- (id) init  


{  



if ( self = [super init] )  



{  


[self setCaption:@"Default Caption"];  


[self setPhotographer:@"Default Photographer"];  


}  


return self; 

上面的代码感觉没啥好解释的,虽然第二行代码好像看上去没啥用。这个是一个单等于号,就是把[super init]的值赋给了self。

它基本上是在调用父类去实现它的初始化。这个if代码段是设置默认值之前验证初始化是否成功。

释放资源Dealloc

这个dealloc方法是在当一个对象希望被从内容里面删除的时候调用。这个我们释放在子类里面引用成员变量的最好的时机:

程序代码:

- (void) dealloc  


{  


[caption release];  


[photographer release];  


[super dealloc];  


} 

开始两行我们发送了release通知给了两个成员变量。我们不要在这里用autorelease。用标准的release更快一点。

最后一行的[super dealloc];非常重要。我们必须要发送消息去让父类清除它自己。假如不这么做的话,这个对象其实没有被清除干净,存在内存泄露。

dealloc在垃圾回收机制下不会被调用到。取而代之的是,我们需要实现finalize方法。

More on Memory Management 

Objective-C的内存管理系统基于引用记数。所有我们需要关心的就是跟踪我们引用,以及在运行期内是否真的释放了内存。

用最简单的术语来解释,当我们alloc一个对象的时候,应该在某个时候retain了它。每次我们调用了alloc或者retain之后,我们都必须要调用release。

///// 。。。图。。。。

这就是引用记数理论。但是在实践的时候,只有两种情况我们需要创建一个对象:

1. 成为一个类的成员变量

2. 只临时的在一个函数里面被使用

在更多的时候,一个成员变量的setter应该仅仅autorelease旧的对象,然后retain新的对象。我们只需要在dealloc的时候调用release就可以了。

所以真正需要做的就是管理函数内部的local的引用。唯一的原则就是:假如我们alloc或者copy了一个对象,那么我们在函数结束的时候需要release或者autorelease它。假如我们是通过别的方式创建的,就不管。

这里是管理成员对象的例子:

程序代码:

- (void) setTotalAmount: (NSNumber*)input  


{  


[totalAmount autorelease];  



totalAmount = [input retain];  



}  


- (void) dealloc  


{  


[totalAmount release];  


[super dealloc];  


} 

这里是本地引用的例子。我们只需要release我们用alloc创建的对象:

程序代码:

NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];  



NSNumber* value2 = [NSNumber numberWithFloat:14.78];  



// only release value1, not value2  


[value1 release]; 

这里是用本地引用对象去设一个成员变量的例子:

程序代码:

NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];  


[self setTotal:value1];  



NSNumber* value2 = [NSNumber numberWithFloat:14.78];  



[self setTotal:value2];  


[value1 release]; 

注意到如何管理本地引用其实都是一样的。不管你是否把它设给了一个成员变量。我们无须考虑setters的内部实现。

相关推荐