方法调用(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
在Objective-C 2.0里面。新添加了一个"."操作的语法。在Mac OS X 10.5里面就使用了Objective-C 2.0语法:
photo.caption = @"Day at the Beach";
output = photo.caption;
我们两种方式都可以使用。可是在一个project里面最好保持风格一致。仅仅使用某一种。
"."操作仅仅可以被使用在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语言而言。创建一个类很easy。它很典型的分成了两个部分。
类的接口通常保存在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方法。
很多其它关于内存管理
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的内部实现。
假设我们非常好的理解了这些的话,我们基本上理解了80%的Objective-C内存管理方面的内容了。
属性Properties
前面我们写caption和author的accessors的时候。你能够已经注意到了代码很简明,应该能够被抽象提取出来。
属性在Objective-C里是一个新的功能。他能够让我们自己主动的生成accessors,另外另一些别的长处。我们能够把上面Photo的类转成用属性来实现:
上面那个类原先的实现是这样:
#import
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end
假如用属性来实现就是这样:
#import
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property (retain) NSString* caption;
@property (retain) NSString* photographer;
@end
@property是Objective-C来声明属性的编译指令。括号中面的"retain"指明了setter须要retain输入的对象。这行其它的部分指定了属性的类型以及名字。
以下让我们来看看这个类的实现:
#import "Photo.h"
@implementation Photo
@synthesize caption;
@synthesize photographer;
- (void) dealloc
{
[caption release];
[photographer release];
[super dealloc];
}
@end
@synthesize指令自己主动的生成了我们的setters和getters。
所以我们仅仅须要实现类的dealloc方法。
Accessors仅仅有当他们原先没有的时候,才会被生成。所以能够放心大胆的去用@synthesize来指定属性。并且能够任意实现你自己的getter和setter。编译器会自己去找哪个方法没有。
属性声明还有别的选项。可是限于篇幅层次,我们下次再介绍。
Logging
在Objective-C里。往console写日记很easy。其实NSLog()跟C语言的printf()两个函数差点儿全然同样。除了NSLog是用额外的“%@”去获得对象。
NSLog ( @"The current date and time is: %@", [NSDate date] );
我们能够log一个对象到console里去。NSLog函数调用要输出对象的description方法。然后打印返回的NSString。我们能够在自己的类里重写description方法。这样我们就能够得到一个自己定义的字符串。
调用nil对象的方法(Calling Methods on Nil)
在Objective-C里,nil对象被设计来跟NULL空指针关联的。他们的差别就是nil是一个对象,而NULL仅仅是一个值。并且我们对于nil调用方法,不会产生crash或者抛出异常。
这个技术被framework通过多种不同的方式使用。最基本的就是我们如今在调用方法之前根本无须去检查这个对象是否是nil。
假如我们调了nil对象的一个有返回值的方法,那么我们会得到一个nil返回值。
我们能够通过nil对象让我们的dealloc函数实现看上去更好一些:
- (void) dealloc
{
self.caption = nil;
self.photographer = nil;
[super dealloc];
}
之所以能够这么做是由于我们给把nil对象设给了一个成员变量。setter就会retain nil对象(当然了这个时候nil对象啥事情也不会做)然后release旧的对象。
这个方式来释放对象事实上更好,由于这样做的话,成员变量连指向随机数据的机会都没有,而通过别的方式,出现指向随机数据的情形机会不可避免。
注意到我们调用的self.VAR这种语法,这表示我们正在用setter,并且不会引起不论什么内存问题。
假如我们直接去设值的话,就会有内存溢出:
// incorrect. causes a memory leak.
// use self.caption to go through setter
caption = nil;
Categories
Categories是Objective-C里面最经常使用到的功能之中的一个。 基本上category能够让我们给已经存在的类添加方法,而不须要添加一个子类。并且不须要知道它内部详细的实现。
假设我们想添加某个framework自带的类的方法,这很有效。假设我们想在我们程序project的NSString可以添加一个方法,我们就行使用category。甚至都不须要自己实现一个NSString的子类。
比方,我们想在NSString里面添加一个方法来推断它是否是一个URL。那我们就能够这么做:
#import
@interface NSString (Utilities)
- (BOOL) isURL;
@end
这跟类的定义很类似。差别就是category没有父类。并且在括号中面要有category的名字。名字能够随便取,可是习惯叫法会让人比較明确category里面有些什么功能的方法。
这里是详细的实现。可是要注意,这本身并非一个推断URL非常好的实现。我们主要是为了总体的了解category的概念。
#import "NSString-Utilities.h"
@implementation NSString (Utilities)
- (BOOL) isURL
{
if ( [self hasPrefix:@"http://"] )
return YES;
else
return NO;
}
@end
如今我们能够在不论什么的NSString类对象里都能够调用这种方法了。以下的代码在console里面打印的"string1 is a URL":
NSString* string1 = @"http://www.CocoaDev.cn/";
NSString* string2 = @"Pixar";
if ( [string1 isURL] )
NSLog (@"string1 is a URL");
if ( [string2 isURL] )
NSLog (@"string2 is a URL");
跟子类不一样。category不能添加成员变量。
我们还能够用category来重写类原先的存在的方法,可是这须要很很小心。
记住,当我们通过category来改动一个类的时候,它相应用程序里的这个类全部对象都起作用。
后记
上面Objective-C的比較基础的大概的讲了一下。Objective-C还是比較好上手的。没有特别的语法须要去学习。并且一些概念在Objective-C里面被重复运用。