原文地址:http://cocoadevcentral.com/d/learn_objectivec/,转载请注明James的iOS讲师之路。
1 调用方法
OC中最基本的调用方法的方式是:
[object method];
[object methodWithInput:input];
方法可以返回结果:
output = [object methodWithOutput];
output = [object methodWithInputAndOutput:input];
同样可以调用类的构造方法,可以用这种方法来创造对象。下面的例子调用了NSString类的String方法,用以返回一个NSString对象:
id myObject = [NSString string];
拥有 id 这个类型表示myObject这个变量可以是任何一种对象的引用,所以,在app编译前,它实现的类和方法都不确定。在这个例子里,很明显对象的类型是NSString, 所以可以改变类型的声明:
NSString* myString = [NSString string];
这样做之后,当我们试图使用一个NSString类型不支持的方法时,编译器就会发出警告了。我们发现NSString类型声明的右边有个星号 - 所有的OC变量都是指针类型。id类型已经预设为指针类型,所以就不用加上星号了。
嵌套信息
在许多语言中,调用嵌套方法或者函数的方式如下:
function1 ( function2() );
The result of function2 is passed as input to function1. In Objective-C, nested messages look like this:
function2的返回值作为输入值给了function1。在OC中,嵌套信息长成下面这个样子:
[NSString stringWithFormat:[prefs format]];
在一行中避免两层以上的嵌套,否则代码就太难以阅读了。
具有多个输入的方法
有些方法需要多个输入值。在OC中,一个方法名可以分成几段。在header文件中,多个输入的方法长成下面这个样子:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
调用这个方法的语句长成下面这个样子:
BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO];
atomically不只是个代号,在运行时系统中,方法名其实就是
writeToFile:atomically:
2 访问器
所有的实例变量在OC中都是默认为私有的。所以在大多数的情况中,你都应该使用访问器来get和set值。访问器有两种语法,传统1.x版本的语法是:
[photo setCaption:@"Day at the Beach"];
output = [photo caption];
第二行的代码不是直接读取实例变量的,实际上,它调用了一个叫做caption的方法。在OC语言的大多数情况中,没有人在getter方法名前面加上get前缀。此外,无论何时,每当你看到方括号中的语句的时候,都是在向一个对象或者一个类发送信息。
点语法
点语法是在Mac OS X 10.5中的OC2.0中被加入的,一样起到了setter和getter的作用。
photo.caption = @"Day at the Beach";
output = photo.caption;
两种语法都可以用,但在一个项目里用相同的语法。注意,点语法应该只用在setter和getter方法上。
3 创建对象
有两种主要的办法来创建一个对象。我们已经看到过第一种了:
NSString* myString = [NSString string];
这是一种较为常规的和自动的风格。在这种风格中,你创建了一个自动释放的对象 - 我们在后边细致分析它。然而,在很多情况下,你需要用手动的风格来创建一个对象。
NSString* myString = [[NSString alloc] init];
我们看出来这是一个嵌套的方法调用。调用的第一个方法是NSString本身的alloc方法。alloc是一个较为底层的方法调用,起到两个作用,预定内存空间、创建对象。调用的第二个方法是新创建对象上的init。一般的,在init方法的实现中,会做一件事,就是基本设置 - 比如说创建实例变量。init的实现细节对于你,也就是这个类的用户,来说,是隐藏的。在某些情况下,你可以使用init的其他版本 - 接受传入参数的版本:
NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];
4 基本内存管理
如果你为Mac OS X写应用的话,你可以打开垃圾回收功能,着基本上代表了在遇到复杂情况前,你就不用担心内存管理的问题了。但是实际情况是,你并不能总是在一个支持垃圾回收的环境中开发应用。所以你还是得知道一些基本概念。如果你使用手动alloc的风格创建了一个对象,你就需要在之后手动的释放这个对象。同理,你不能手动释放一个自动释放的对象,否则应用就会崩溃。下面是两个例子:
// string1 会被自动释放
NSString* string1 = [NSString string];
// 必须记得“事后”手动释放
NSString* string2 = [[NSString alloc] init];
[string2 release];
作为入门者,你可以假想自动对象在完成当前的功能后就自动释放了。内存管理还有一些别的要学的,但还需要我们一会儿看一些别的概念后再讲。
5 设计一个类的接口(interface)
注意这个接口和JAVA的接口不是一个概念。OC中的接口(interface)的作用是"声明"一个类。
OC语法中,创建一个对象是非常简练的。典型的有两部分工作要做。一 - 类的接口通常存在ClassName.h文件里,它定义了实例变量和公共方法。二 - 实现方式写在ClassName.m文件里,它包含了那些方法的实际实现代码。m文件里通常还会定义一些私有方法,其它的类不用使用一个类的私有方法。一个接口长成下面这个样子。这个类的名字叫Photo,所以文件名就是Photo.h
#import
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@end
我们首先导入了Cocoa.h,这样就等于我们把所有的Cocoa app的基本类都拉进来了。#import命令有自动保护的功能,能防止一个文件被多次包含进来。@interface关键字指示这是一个对于Photo这个类的声明文件。冒号指示出父类,在这里,Photo的父类是NSObject。在大括号里面有两个变量,caption和photographer。现在两个都是NSString类型的。其实在这里,它们可以是任何类型的,包括id。最后,@end符号指示类声明结束了。
添加方法
我们来个实例变量加一些getter方法:
#import
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- caption;
- photographer;
@end
别忘了,典型的OC方法不写get前缀。方法前面写出的减号代表这是一个实例方法,如果是个加好,代表是个类的方法。编译器默认一个方法返回一个id类型,并且所有接收的参数也都是id对象。上边的例子,技术上是正确的,但是不常见。我们现在给他加上一些返回值:
#import
@interface Photo : NSObject{
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
@end
现在我们再加上一些setter:
#import
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end
setter不需要返回什么值,所以把他们声明为void。
6 类的实现
我们来实现一个类,从getter方法们开始:
#import "Photo.h"
@implementation Photo
- (NSString*) caption {
return caption;
}
- (NSString*) photographer {
return photographer;
}
@end
这一段代码从@implementation和类名称开始,以@end结束。和类接口的写法相同。所有的方法都必须在这两个语句之间出现。getter们比较简单,我们多说两句setter们:
- (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;
}
但是,假如你不能使用垃圾回收机制,你就需要释放旧的对象,然后得到新的。实际上有两种方法来释放一个对象的引用:释放 - release 和自动释放 - autorelease。在一个标准的释放中,引用立即被移除;在一个自动释放中,引用会在当前的函数执行完毕后才被移除(当然你也可以手动添加代码来改变这一点)。在一个setter里,要使用比较安全的autorelease,因为新变量和旧变量的值可能指向同一个对象。你总不想马上释放一个想要留存的对象吧?上面这段话现在看起来可能有些吃力,没关系,我们后边还会遇到它。现在不用着急完全理解。
init
我们可以创建一个init方法来为实例变量赋初始值:
- (id) init{
if ( self = [super init] ) {
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}
这段代码比较容易理解。重点解释第二行,这一行只是让父类做自己的初始化,然后把结果传给self。只有当这个动作成功时,init方法才会对自己的默认值做出设置。
dealloc
当需要从内存中去除某个对象时,要调用它的deaclloc方法。这时是释放所有子实例的引用的最好时机:
- (void) dealloc {
[caption release];
[photographer release];
[super dealloc];
}
方法内的前两行,我们把release信息发到对象的两个实例变量中。我们不需要使用autorelease,标准的release速度会快一些。最后一行非常重要。我们需要发送dealloc信息给父类,让他自己进行清洁工作。不做这件事的话,这个对象就不会被移除,就会造成内存泄漏。如果开启了垃圾回收,dealloc方法就不会被调用。如果开启了垃圾回收,你就需要实现finalize方法。
7 更多内存管理的内容
OC的内存管理系统叫做引用计数。你只要把引用跟踪好,系统运行时会自动做出实际的内存释放。用简单的话说,你alloc一个对象,然后在每次发alloc或retain信息后发个release信息。所以如果你用了一次alloc和一次retain,你就要发两次release。
这就是引用计数的理论。但在实际操作中,通常只有两个原因来创建一个对象:
1. 用它来做一个实例变量
2. 在一个函数中临时用它一次
在大多数情况中,一个实例变量的setter方法应该autorelease旧的对象,然后retain新的。所以,你只需要保证在dealloc中也release它就好了。所以,真正的工作只剩一项:在一个函数中管理本地引用。这里只有一条规矩:如果你用alloc或者copy创建了一个对象,在函数最后向它发送一个release或者autorelease。如果你是用其它办法创建的这个对象,什么也不用做。举个栗子,管理一个实例变量:
- (void) setTotalAmount: (NSNumber*)input {
[totalAmount autorelease];
totalAmount = [input retain];
}
- (void) dealloc {
[totalAmount release];
[super dealloc];
}
另外一个例子,本地引用。我们只需要对alloc创建的对象发送release:
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];
// 只需要对 value1 "负责"
[value1 release];
来个连招:用一个本地引用来对一个实例变量赋值
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
[self setTotal:value1];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];
[self setTotal:value2];
[value1 release];
注意到不管你是否将本地引用设置成实例变量,管理它们的方法都是一样的。你不需要考虑setter们是怎么实现的。理解了这一点,OC内存管理需要你了解的东西你基本上就理解到90%了。
8 日志
在OC中,向控制台输出日志是非常方便的。实际上,NSLog()函数和C语言的printf()函数基本上一样,除了它多了一个%@标志以外。
NSLog ( @"The current date and time is: %@", [NSDate date] );
你可以向控制台发送一个对象。NSLog函数会调用这个对象的description方法,打印其返回的NSString。你可以在你的类中重写description方法来返回一个定制的字串。
9 属性
当我们之前写caption和author访问器方法的时候,你也许注意到了代码很”原始“,还有一般化的空间。属性是OC的特性,它能够自动生成访问器以及其他一些好处。我们来将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是OC用来声明属性的指令。括号里的retain指明了setter需要去获得传入值,行中剩下的部分只是用来指明属性的类型和名称。下面看看类的实现:
#import "Photo.h"
@implementation Photo
@synthesize caption;
@synthesize photographer;
- (void) dealloc {
[caption release];
[photographer release];
[super dealloc];
}
@end
@synthesize指令自动生成setter们和getter们。所以,我们只需要把这个类中剩下的dealloc方法实现就好了。访问器只有在尚不存在的时候才会被创建,所以可以放心大胆地为属性指示@synthesize,然后如果需要的话,去实现自己特定的setter和getter们。编译器会自动填补没有实现的方法。还有很多其他的办法来声明属性,我们就不在此讨论了。
10 调用nil的方法
在OC中,nil对象和其他语言中的空指针(NULL)有相同的功能。但一个区别在于你可以调用nil的方法而不至于让程序崩溃或者扔出一个异常。这个技术在整个框架中的多个方面得以应用,但对于你来讲最显著的好处是,在调用一个对象的方法前,不用检查它是否为空了。nil对象的方法的返回值还是nil。使用这个特性,我们还能优化一下我们的dealloc方法:
- (void) dealloc {
self.caption = nil;
self.photographer = nil;
[super dealloc];
}
我们把nil设入一个实例变量时,setter只是保留一个nil同时释放旧的值。这样做避免了变量指向对象从前位置的"野"数据,所以这个方法通常比dealloc更优。我们用到了self.语法,意味着我们在用setter的同时“免费”使用了内存管理。如果我们直接设值,会引起内存泄漏:
// 错误。引起内存泄漏。
//使用self.caption来间接调用setter
caption = nil;
11 分类
Categories are one of the most useful features of Objective-C. Essentially, a category allows you to add methods to an existing class without subclassing it or needing to know any of the details of how it's implemented. This is particularly useful because you can add methods to built-in objects. If you want to add a method to all instances of NSString in your application, you just add a category. There's no need to get everything to use a custom subclass. For example, if I wanted to add a method to NSString to determine if the contents is a URL, it would look like this:
分类是OC中最有用的特性之一。简单说,一个分类允许你不用继承,甚至连实现细节都不知道的情况下,对于一个现存的类添加方法。有了这个特性,你就可以对OC内建的类扩充方法了。比如,如果你想在自己的工程中对NSString所有的实例添加一个方法,那么加个分类就可以了。有了它,我们不用什么事都要通过集成来实现了。举个栗子,如果我想给NSString添加个方法,用来检测一个字串是不是一个URL,我就可以这样写:
#import
@interface NSString (Utilities)
- (BOOL) isURL;
@end
是不是非常像声明一个类?区别在于不需要把父类列出来,此外类别的名称写在括号里。可以为类别起任何名字,但最好能够反映里面方法的作用。下面是实现。注意这不是一个好的检测URL的实现办法 - 我们只是为了给类别举个样例而已:
#import "NSString-Utilities.h"
@implementation NSString (Utilities)
- (BOOL) isURL{
if ( [self hasPrefix:@"http://"] )
return YES;
else
return NO;
}
@end
现在你就可以对任何一个NSString用这个方法了。下面的代码会在控制台中显示“string1 is a URL”:
NSString* string1 = @"http://pixar.com/";
NSString* string2 = @"Pixar";
if ( [string1 isURL] ) NSLog (@"string1 is a URL");
if ( [string2 isURL] ) NSLog (@"string2 is a URL");
于子类不同,分类不能添加实例变量。然而你可以用分类来重写一个类现有的方法,但需要格外地小心。记住,当你对一个类使用分类在做改变的时候,整个应用内所有这个类的实例都会受到影响。
总结
这是一个基本的OC概览。正如你看到的,这门语言入手还是挺容易的,没有什么特别的语法结构要学,所用到的编码惯例也是在Cocoa里面反复用到的。
原网址最后有所有讲义中源代码的下载链接。
谢谢观赏