learn_objectivec翻译

原文链接:learn_objectivec

#Objective-C
Objective-C (简写:OC)是写Mac软件的基础语言。如果你熟悉面向对象和C语言,那么OC对你来说将不那么陌生。如果你还没学过C语言,建议你先看看C Tutorial

#PART 1 - Calling Methods
让我们先看几个例子来迅速了解OC。
调用对象的方法的基础语法是这样子的:

1
2
[object method];
[object methodWithInput:input];

有返回值的调用:

1
2
output = [object methodWithOutput];
output = [object methodWithInputAndOutput:input];

同时你可以使用类方法直接调用。下面的例子中,我们将使用NSString直接调用string方法,返回一个新建的NSString对象:

1
id myObject = [nssTRING STRING];

类型id为最原始类型,表示变量myObject适用于任何类型,所以这么写的话,你在编译这段代码的时候并不知道它真实的类型是什么。
很明显,这个例子中的对象其实是一个NSString类型,所以我们可以这么写:

1
NSString* myString = [NSString string];

现在这是个NSString类型了,编译器就会警告你试图调用的某些方法NSString是不支持的。
注意对象类型右边的*号,所有的OC变量都是指针类型。类型id是一个预定义的指针类型,所以不用写星号

##Nested Messages
很多语言都有嵌套方法或者方法调用,就像这样子:

1
function1 ( function2() );

funtion2的返回值传给function1。在OC中嵌套机制如下:

1
[NSString stringWithFormat:[prefs format]];

为了避免难以阅读,请尽量不要在单行中嵌套调用超过两层。

##Multi-Input Methods
有些方法有输入多个参数。OC中一个方法名可以被拆分成几个部分,在header中多个输入参数的方法声明如下:

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

调用上面的方法如下:

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

这些方法不仅仅是命名参数,在运行时他们确实也是叫writeTofile:atomically:

#PART 2 - Accessors
OC默认所有的变量都是私有的(private),所以大部分情况下你需要使用get和set来访问。OC有两种语法。
传统的1.x语法:

1
2
[photo setCaption:@"Day at the Beach"];
output = [photo caption];

实际上,代码中第二行并不是直接读取该实例的值,而是调用了一个叫caption的方法。同时在OC中,在调用getters方法时,大部分情况下不必显示地写前置get
任何你看到的方括号的代码,其实都是发送一个信息给一个对象(object)或者类(class)。

##Dot Syntax
Mac OS X 10.5中OC2.0引进了getters和setters的点语法:

1
2
photo.caption = @"Day at the Beach";
output = photo.caption;

You can use either style, but choose only one for each project。点语法只能用于setters和getters,不能用于构造方法。

#PART 3 - Creating Objects
有两种主要的构造方法,一个是上文提到过的:

1
NSString* myString = [NSString string];

这是更方便自动化的方式。这种方式下,你创建了一个自动释放的对象,之后我们会深入讲它。然而,大部分情况下,你得手动的方式创建一个对象:

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

它是一个方法的嵌套调用。第一个方法alloc是类方法,NSString直接调用。这是一个相对低级的调用,它分配了内存并实例化了一个对象。
第二个部分是新建的对象调用init。初始化的工作通常是最基本的组成,比如创建实例的对象。不过其内部实现对使用者是透明的,使用者无须了解具体实现。
有时候,你或许会用到不一样的带输入参数的init:

1
NSNumber* value = [[NSNumeber alloc] iniWithFloat:1.0];

#PART 4 - Basic Memory Management
如果你要写一个Mac OS X的应用,你可以选择性的允许垃圾收集。这意味着,通常你不用关心内存管理,直到你遇到复杂的业务。
然而,你不一定总是工作于支持垃圾收集机制的环境下。这种情况下,你得知道些基础概念。
如果你创建了一个手动alloc的对象,你之后得relase。但是对于自动释放的对象,你不能手动释放,这样会报错。
有两个例子:

1
2
3
4
5
6
// string1 will be released automatically
NSString* string1 = [NSString string];

// must release this when done
NSString* string2 = [[NSString alloc] init];
[string2 release];

从这部分引导,你了解了自动化的对象在当前的方法结束后会消失。
虽然关于内存管理还有更多内容要学习,不过在了解更多的概念之后学习将更有意义。

#PART 5 - Designing a Class Interface
OC创建一个类非常简单。通常两部分来实现。
接口类通常保存在ClassName.h文件中,其定义了实例变量和公共的方法。
ClassName.m文件是接口的真正实现类,它包含了方法的真实实现。通常方法会被定义为私有,这样类的调用者不能访问。
这里有个接口文件的样式,这个类叫做 Photo ,所以该文件名为Photo.h:

1
2
3
4
5
6
7
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@end

首先,我们导入Cocoa.h来导入Cocoa 应用所需要的基本类。#import命令自动防止单个文件导入多次。
@interface表明这是一个类Photo的声明,冒号具体说明它的父类是一个NSObject类型。
花括号中有两个变量:captionphotographer,两个都是NSString类型,不过他们可以是任意类型,包括类型 id。
最后,@end标记结尾,结束声明。

Add Methods

我们来为上述实例添加几个getters方法:

1
2
3
4
5
6
7
8
9
10
11
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}

- caption;
- photographer;

@end

记住OC方法通常去掉前置”get”。方法前一个单独的减号表示此方法为实例方法;而一个加号则表示它是一个类方法。
默认情况下,编译器假定方法返回一个id对象,输入的对象也是id类型。上述代码在理论上是正确的,不过没有实际意义。让我们添加指定类型的返回值:

1
2
3
4
5
6
7
8
9
10
11
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}

- (NSString*) caption;
- (NSString*) photographer;

@end

现在添加setters:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;

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

@end

Setters不需要返回值,所以我们声明为void

#PART 6 - Class Implementation
创建一个有getters的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "Photo.h"

@implementation Photo

- (NSString*) caption {
return caption;
}

- (NSString*) photographer {
return photographer;
}

@end

类似接口上面代码由 @implementation 加类名开头,以@end 结尾。所有代码都得在两个声明之间。
如果你写过代码你会觉得getter看起来非常熟悉,所以让我们移步到setters展开说明:

1
2
3
4
5
6
7
8
9
10
11
- (void) setCaption: (NSString*)input
{
[caption autorelease];
caption = [input retain];
}

- (void) setPhotographer: (NSString*)input
{
[photographer autorelease];
photographer = [input retain];
}

每个setter处理两个变量,第一个是指向原有对象的引用,第二个是新的输入对象。在垃圾收集环境下,我们只要直接复制就好:

1
2
3
- (void) setCaption: (NSString*)input {
caption = input;
}

但是如果不能使用垃圾收集机制,你需要relase释放旧的对象,retain保留新的。
有两种方式释放引用:releaseautorelease。标准的release释放将立即移除引用。autorelease方法将会尽快释放,但是它至少会保持到该方法结束(除非你特别的自定义编码改变它)。
因为新对象和旧对象不会指向同一个对象,所以自动释放在setter内部是安全的。你并不想立即释放一个你想保留的对象。
现在或许会觉得困惑,等继续深入之后你会慢慢理解这些困惑。所以你现在完全不理解也没有关系。

Init

我们可以创建一个init方法来初始化我们的实例变量:

1
2
3
4
5
6
7
8
9
- (id) init
{
if ( self = [super init] )
{
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}

这简直是自我解释的意思,虽然第二行看起来有点怪异,这个等号意思是说把[super init]的返回值赋值给自己。
这本质上就是调用父类来构造自己。而if声明则是指在你尝试设置默认值前判断初始化是否成功。

##Dealloc
dealloc方法会在对象从内存中被移除的时候调用。使用此方法的最佳时机是你想释放所有子对象变量的时候:

1
2
3
4
5
6
- (void) dealloc
{
[caption release];
[photographer release];
[super dealloc];
}

前两行代码,我们仅仅释放每个实例变量。这里我们不使用自动释放,因为标准的释放会快一点。
最后一行非常重要,我们必须发送[super deallo]消息,让父类做清理。如果没有调用的话,对象不会被移除,这将导致内存泄漏。
开启了垃圾回收机制时,dealloc方法不会被调用。相反,你应该实现finalize方法。

#PART 7 - More on Memory Management
OC的内存管理模型称为引用计数。你所要做的就是观察引用的轨迹,并在运行时准确的释放内存。
对象最简单的生命周期中,你alloc分配了一个对象,或许在后面某个节点retain保留了该对象,接着你给所有 alloc/retain发送release信息来释放。所以,如果你alloc一次然后retain一次,你就得release两次。

内存模型
上面是理论的引用计数。在实际医用中,通常只有两种情况需要创建对象:

  1. 保持一个对象的变量
  2. 在方法中用于临时场景
    大部分情况,为一个对象setter变量只要autorelease旧对象,retain新对象。同时你要保证在dealloc也释放。
    所以真正的工作是管理在方法内部的本地引用。这只有一个规则:如果你通过alloccopy创建一个对象,你需要在方法结束之后发送releaseautorelease信息。如果你使用其他任何方式创建对象,大可什么都不要做。
    下面是第一个案例,管理实例化变量:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - (void) setTotalAmount: (NSNumber*)input
    {
    [totalAmount autorelease];
    totalAmount = [input retain];
    }

    - (void) dealloc
    {
    [totalAmount release];
    [super dealloc];
    }

下面是另一个案例,本地引用。我们只要释放通过alloc创建的对象:

1
2
3
4
5
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];

// only release value1, not value2
[value1 release];

下面的案例会使用冒号:一个本地对象赋值一个实例化的变量:

1
2
3
4
5
6
7
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
[self setTotal:value1];

NSNumber* value2 = [NSNumber numberWithFloat:14.78];
[self setTotal:value2];

[value1 release];

注意到这与管理本地引用是完全一致的,你无须关心是否为实例化变量赋值。你不需要在意setters是如何实现的。
如果你明白了这一点,你就已经掌握了90%的OC内存管理了。

#PART 8 - Logging
OC中在控制台打印信息非常简单。除了额外的%@用来指代对象外,OC的NSLog()方法与C语言的printf()方法几乎一模一样:

1
NSLog ( @"The current date and time is: %@", [NSDate date] );

你可以在控制台打印一个对象。NSLog方法调用对象的description方法打印返回的NSString类型数据。你可以重载description方法,返回想要的字符串。

#PART 9 - Properties
还记得我们在写caption和author访问的方法吗,你也许已经注意到了那些代码非常简单并且是可以被动生成的。
Properties(属性)是OC的一个语法,将为你自动整合访问私有变量的代码,同时它还有一些其他好处。让我们用properties改造下Photo类。
原来的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;

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

@end

用properties改造之后的代码如下:

1
2
3
4
5
6
7
8
9
10
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property (retain) NSString* caption;
@property (retain) NSString* photographer;

@end

@property是OC声明了属性的一个命令。圆括号中的”retain”声明了setter应该保留输入的值,剩余的代码就是简单的声明属性名和类型。
现在我们来看下实现部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "Photo.h"

@implementation Photo

@synthesize caption;
@synthesize photographer;

- (void) dealloc
{
[caption release];
[photographer release];
[super dealloc];
}

@end

@synthesize命令自动为我们自动生成了setters和getters方法,所以我们所要做的就是实现dealloc方法就可以了。
只有在不存在的时候访问方法才会被创建,所以如果你想生成setters和getters,请放心为属性声明@synthesize,如果有缺少访问方法的话编译器会自动填充。
属性声明还有很多其他选项,不过这已经超过本引导的范围,故不做讲解。

#PART 10 - Calling Methods on Nill
OC中,nil对象功能等价于其他语言的NULL指针。不同点在于如果你使用nil对象调用方法的话不会闪退或抛出异常。
这个技术在框架中被用于很多不同的实践,不过现在主要的一点就是你使用一个对象调用其方法时不需要再验证是否为空了。如果你使用nil对象调用一个方法,它将返回nil。
因此我们还可以略微的改进下我们的dealloc方法:

1
2
3
4
5
6
- (void) dealloc
{
self.caption = nil;
self.photographer = nil;
[super dealloc];
}

上述代码能够运行是因为我们给实例化的变量赋值nil时,setter仅仅保留了nil并释放了旧的值。对于deallloc来说是更好的解决方式,因为变量没有机会指向随机的曾今已有的对象。
注意到这里我们用的是self.<var>语法,意味着我们是通过setter方法获得内存管理并释放的。如果我们之间像下面那样直接赋值,将会导致内存泄漏:

1
2
3
// incorrect. causes a memory leak.
// use self.caption to go through setter
caption = nil;

PART 11 - Categories
Categories 是OC中最有用的功能了。大体意思,category可以让你不用继承一个已经存在的类就可以为那个类添加方法,or needing to know any of the details of how it’s implemented。
这相当有用,你可以为一个内建的对象添加方法。比如你可以为你项目中所有的NSString对象添加一个类别,却不用自定义一个超类。
例如,如果我想在NSString添加一个验证传入的内容是否是一个URL,你可以这样写:

1
2
3
4
5
#import <Cocoa/Cocoa.h>

@interface NSString (Utilities)
- (BOOL) isURL;
@end

这非常像一个类的声明,不同点在于没有父类,而是一个圆括号中的类别名。类别名用于方法调用,名字没有限制可以随意取。
下面是其实现方式。不过这不是一个好的辨别URL的方式,我们只是为了阐述categories访问这个概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "NSString-Utilities.h"

@implementation NSString (Utilities)

- (BOOL) isURL
{
if ( [self hasPrefix:@"http://"] )
return YES;
else
return NO;
}

@end

现在你所有NSString对象都可以使用这个方法。下面的代码将在控制台打印”string1 is a URL”:

1
2
3
4
5
6
7
8
NSString* string1 = @"http://pixar.com/";
NSString* string2 = @"Pixar";

if ( [string1 isURL] )
NSLog (@"string1 is a URL");

if ( [string2 isURL] )
NSLog (@"string2 is a URL");

不像子类,类别不可以添加实例变量。但是你可以重载类中已有的方法,即便如此你还是得十分谨慎的使用。
记住你使用类别改变一个类,整个应用中使用到这个类的实例都会被影响到。

#Wrap Up
以上就是OC的基本概要了,正如你所看到的,OC是相当简单易学的,没有那么多特别的语法需要学习,and the same conventions are used over and over again throughout Cocoa.
如果你迫不及待的想了解这些例子,通过下面的链接下载,查看源码:
LearnObjectiveCXcode 3.0 Project(56k);


谢谢。