說明,這是網友翻譯的一篇總結Objective‐C文法的文章,我覺得原文寫得不錯,翻譯得也可以,所以直接放到這裡給大家看了。
中文PDF檔案下載可點擊 這裡 。
====================================== 分隔線 ====================================
Learn Objective‐C
原文地址 http://cocoadevcentral.com/d/learn_objectivec/
譯者前言
在網上看到這篇文章覺得寫的很不錯,但是貌似沒有人翻譯成中文,我就大膽翻譯了。本人做軟體開發6年了,但是大多數是在Windows平台上,最近才在我的機器上裝了一個leopard,在Objective-C上也是一個新手,對於本文翻譯的是否到位心裡還在打鼓。如果有覺得翻譯不對不好的地方,請與我聯絡,我的油箱是:cchenhao at gmail dot com。
cc很好J,謝謝大家。
Objective-C
Objective‐C是開發Mac軟體的主要程式設計語言。如果你瞭解一些物件導向的基本概念和C語言,那麼會對你學習Objective‐C有很多協助,如果你不瞭解C,那麼建議你先讀一下C 指南。
這篇指南由Scott Stevenson編寫並做圖。
方法調用
為了儘快開始,讓我們先看一些例子。
調用一個對象的方法的基本文法是這樣的:
[object method];
[object methodWithInput:input];
方法可以有傳回值:
output = [object methodWithOutput];
output = [object methodWithInputAndOutput:input];
你還可以調用類的方法,這也是建立一個對象的辦法。在下面的例子裡,我們調用了NSSting類的string方法,用來返回一個新的NSString類的對象。
id myObject = [NSString string];
id類型意味著變數myObject可以是任意類型的對象。所以,當你編譯這段代碼時,它的實際類型以及它所實現的方法編譯器是不知道的。在我們的例子裡,很顯然對象的類型是NSString,所以我們可以改變對象的型別宣告:
NSString* myString = [NSString string];
現在,這就是一個NSString類型的變數了,如果我們在這個對象上調用NSString類型對象不支援的方法,編譯器就會發出警告。
注意:在物件類型的右面有一個星號(*),在Objective-C中,所有的物件變數都是指標類型。id類型已經被預定義為指標類型,所以不需要加一個星號。
嵌套調用
在許多程式設計語言中,嵌套的方法或函數調用像是這樣的:
function1(function2());
function2的傳回值做為輸入參數傳遞給function1。在Objective-C中,嵌套調用看上去像是這樣的:
[NSString stringWithFormat:[prefs format]];
要盡量避免在一行語句中進行兩層以上的嵌套,這樣會使代碼的可讀性降低。
多輸入參數的方法
一些方法需要多個輸入參數。在Objective-C中,一個方法的名字可以被拆分成幾段,在標頭檔中,多輸入參數的方法聲明看上去像是這樣的:
-(BOOL)writeToFile:(NSString *)path
atomically:(BOOL)useAuxiliaryFile;
你可以這樣調用這個方法:
BOOL result = [myData writeToFile:@”/tmp/log.txt” atomically:NO];
這些不是具名引數。在運行時環境中,該方法的名字實際上是 writeToFile:atomically:
訪問器
在Objective‐C中,所有的執行個體變數預設都是私人的,所以,在大多數情況下,你應該使用訪問器來擷取或設定這些變數的值。現在有兩種文法。下面的是傳統的1.x文法:
[photo setCation:@”Day at the Beach”];
output = [photo caption];
第2行代碼不是直接讀取執行個體變數。實際上它是在調用名為caption的方法。在Objective-C中,大多數情況你不用在擷取器(getter)前面添加一個”get”首碼。在任何情況下,在方括弧中代碼都意味著你是在給一個對象或者一個類型發送一個訊息(即一個方法調用)。
點操作符
在Mac OS X 10.5中,Objective-C 2.0新增了點操作符的設定器(setter)和擷取器(getter):
photo.caption = @”Day at the Beach”;
output = photo.caption;
兩種文法你可以使用任何一種,但是在一個項目中最好只使用一種。同時,點文法只能使用在設定器(setter)和擷取器(getter)上,而不能用於普通方法。
建立對象
建立對象有兩種主要的辦法。第一個是之前你看到的:
NSString* myString = [NSString string];
這是一種更加方便自然的方式。通過這種方法,你建立了一個自動釋放(autoreleased)的對象,這一點我們會在後面看到更多的細節。儘管如此,在許多地方,你可能需要通過手工建立的方式來建立一個對象,如下:
NSString* myString = [[NSString alloc] init];
這是一個嵌套的方法調用。第一個是NSString類本身的alloc方法調用。這是一個相對低層的調用,它的作用是分配記憶體及執行個體化一個對象。第二個是調用新建立對象的init方法。init方法通常做對象的初始化設定工作,比如建立執行個體變數。作為一個類的使用者,你無法知道這些方法的實現細節。在某些情況下,你可以使用init方法的另外一種版本,這些版本帶有輸入參數:
NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];
記憶體管理基礎
當你為Mac OS X編寫應用程式時,你可以選擇允許記憶體回收。這意味著如果不是在特別複雜的情況下,你不用考慮記憶體管理。然而,你並不會總是工作在支援記憶體回收的環境中。這樣的話,你就需要知道一些基本概念。如果你通過手工alloc的方式建立一個對象,之後你需要release這個對象。同樣,你也不能手工釋放(release)一個能自動釋放(autoreleased)的對象,因為這將會使你的應用程式崩潰。
以下是兩個例子:
//string1 將被自動釋放
NSString* string1 = [NSString string];
//必須在用完後手工釋放
NSString* string2 = [[NSString alloc] init];
[string2 release];
在這裡,你可以認為自動釋放對象會在當前函數結束的時候被自動釋放。
關於記憶體管理要學的東西很多,但是我們先瞭解一下其他的概念,這樣我們會有更多的認識。
設計類介面
在Objective-C的文法中,建立一個類是非常簡單的。一個類通常分為兩部分。類的介面(interface)通常存放在類似ClassName.h的檔案中,在這裡,我們定義執行個體變數和公用(public)方法。類的實現存放在ClassName.m這樣的檔案中,它包含了這些方法的實際實現代碼。它通常還定義了客戶類不能訪問的私人(private)方法。
一個介面檔案看上去像以下這樣的。這個類名字叫做Photo,所以介面檔案名稱是Photo.h:
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@end
首先,我們匯入了Cocoa.h,目的是將Cocoa應用程式的基本類添加進來。#import指令會自動防止將同一個檔案匯入多次。@interface表明這是類Photo的聲明。冒號後面指定父類(superclass),這裡父類是NSObject。在花括弧裡面聲明了兩個執行個體變數:caption和photographer。都是NSString類型,執行個體變數可以是任何物件類型,包括id類型。最後,@end符號結束類的聲明。
添加方法
讓我們給執行個體變數添加一些擷取器(getter)
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
caption;
- photographer;
@end
記住,Objective-C語言中通常省略方法的“get”首碼。方法名字前面的單個減號(-)表明該方法是一個執行個體方法。如果方法名字前面是一個加號(+),則表明該方法是一個類(static)方法。
編譯器會預設一個方法的傳回值是一個id類型的對象,所有的輸入參數也預設是id; 類型。上述代碼在技術上是正確的,但是我們一般不這樣寫,我們需要給這些方法指定傳回值類型。
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*)caption;
- (NSString*)photographer;
@end
現在,我們來添加設定器(setter):
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*)caption;
- (NSString*)photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end
設定器不需要有傳回值,所以我們指定傳回值是void。
類實現
現在,我們從擷取器(getter)開始,來建立一個類的實現。
#import "Photo.h"
@implementation Photo
- (NSString*) caption {
return caption;
}
- (NSString*) photographer {
return photographer;
}
@end
這段代碼以@implementation和類的名字開始,並且像介面一樣,有一個@end。所有的方法必須寫在這兩條語句之間。如果你寫過代碼,就會覺得上面的擷取器看上去很熟悉,所以我們還是來看一看設定器,它們需要多一點解釋。
- (void) setCaption: (NSString*)input
{
;
caption = [input retain];
}
- (void) setPhotographer: (NSString*)input
{
[photographer autorelease];
photographer = [input retain];
}
每一個設定器都要處理兩個變數,第一個是當前引用的對象,第二個是新輸入的對象。在帶有記憶體回收機制的環境中,我們可以直接設定成新的值。
- (void) setCaption: (NSString*)input
{
caption = input;
}
但是,如果你不能使用記憶體回收,你需要release舊的對象,並且retain新的對象。釋放一個對象的引用實際上有兩種方法:release 和 autorelease。標準的release會立刻釋放對象的引用。autorelease會等一會兒才釋放,但是引用實際上會一直存在,直到當前方法結束(除非你添加自訂的代碼來明確的改變它)。在設定器裡面使用autorelease方法會更加安全一些,因為要改變的變數的新舊兩個值可能指向的是同一個對象。而你可能不希望立刻釋放實際上你要保留的對象。現在,這看上去有點讓人迷惑,但是隨著你的不斷學習,你就會有更多的認識。所以,現在不必徹底的理解這些。
Init
我們可以建立一個init方法用來給我們的執行個體變數設定初始化值:
- (id) init
{
if ( self = [super init] )
{
[selfsetCaption:@"Default Caption"];
[selfsetPhotographer:@"Default Photographer"];
}
return self;
}
這段代碼是完全不需要加以說明的,儘管第二行看上去有點不常見。它是一個單個的等號(=),作用是將[super init]的結果賦值給self。這實際上是要求父類做(父類的)初始化操作。if語句的作用是在嘗試設定(本對象的)預設值之前驗證父類是否初始化成功。
Dealloc
dealloc方法在一個對象從記憶體中刪除時被調用。通常在這個方法裡面釋放所有對象裡的執行個體變數。
‐ (void) dealloc
{
;
[photographer release];
[superdealloc];
}
在前兩行,我們直接調用了執行個體變數的release方法。在這裡,我們不需要使用autorelease,因為標準的release更快一些。最後一行非常重要,我們發送了一個[super dealloc]訊息,要求父類做清理工作。如果我們不做的話,該對象就不會被從記憶體中刪除,這就造成了記憶體泄露。當啟用記憶體回收機制時,對象的dealloc方法不會被調用。此時,你可以實現一個finalize方法來代替它。
記憶體管理
Objective-C的記憶體管理是基於引用計數的。你要做的事情只是關注你的引用,而釋放記憶體的工作實際上由運行環境完成。在最簡單的情形中,你分配的(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建立的對象就行了:
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];
注意,不論你是不是把本地引用當成執行個體變數一樣賦值,管理它們都是完全相同的。你不必考慮設定器(setter)是如何?的。如果你理解了這些,你就理解了關於Objective-C記憶體管理中90%你需要知道的內容。
日誌記錄
在Objective-C中,將日誌資訊輸出到控制台是非常簡單的。實際上NSLog()函數很像C語言裡面的printf()函數,除了要用一個%@符號代表一個對象。
NSLog ( @"The current date and time is: %@", [NSDate date] );
你可以將一個對象的資訊作為日誌在控制台輸出。NSLog函數調用該對象的description方法,並且將這個方法返回的NSString列印到控制台。你可以在你的類中重寫這個方法以返回你自訂的字串。
屬性(Properties)
前面我們寫了caption和author的存取方法,你可能也注意到了,那些代碼很簡單,應該可以寫成具有更普遍意義的形式。屬性是Objective-C的一個特性,它允許我們自動產生訪問器,同時還有其它方面的好處。我們用屬性來改寫一下Photo類。
之前的代碼看上去是這樣的:
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
-xa (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end
用屬性改寫後的代碼看上去是這樣的:
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property (retain) NSString* caption;
@property (retain) NSString* photographer;
@end
@property是Objective-C語言的一個指令,通過它聲明屬性。帶括弧的”retain”指示設定器(setter)要保留輸入值,該行後面的是指定屬性的類型以及名稱。下面我們看看這個類的實現部分:
#import "Photo.h"
@implementation Photo
@synthesize caption;
@synthesize photographer;
- (void) dealloc
{
;
[photographer release];
[super dealloc];
}
@end
@synthesize指令為我們主動產生了setter和getter,所以我們必須要做的就只有實現dealloc方法了。只有當訪問器不存在的時候,@synthesize才會自動產生訪問器,所以,即使是使用@synthesize聲明了一個屬性,你仍然可以實現自訂的getter和setter。編譯器只會自動產生你沒有自訂的方法。關於屬性的聲明還有很多選項,但是它們超出了本指南的範圍。
在Nil上調用方法
在Objective-C中,nil對象的作用等同於很多其他語言的NULL指標。不同的地方在於,在nil上調用方法不會導致程式崩潰或拋出異常。這種技術被用在很多地方,但是對於我們來講,最主要的就是我們不用在調用一個對象的方法之前檢查該對象是否為空白。如果你調用了一個nil對象的方法並且該方法有傳回值的話,你會得到一個nil傳回值。我們也可以用它來稍微改進一下我們的dealloc方法:
- (void) dealloc
{
self.caption = nil;
self.photographer = nil;
[super dealloc];
}
可以這樣做是因為當我們將nil賦值給一個執行個體變數,設定器(setter)會釋放舊對象並且保留(retain)nil對象。這種做法對於dealloc來說更好一些,因為這樣做避免了讓變數指向一個隨機的資料,而這個資料又恰好是另外一個對象。注意,我們在這裡使用了self.<var>文法,這表示我們使用的是setter,它會進行記憶體管理。如果我們僅僅是直接設定值,像下面這樣,那就會產生記憶體泄露:
// incorrect. causes a memory leak.
// use self.caption to go through setter
caption = nil;
類目(Category)
類目是Objective-C中最有用的一個特性。實質上,類目允許你為一個已存在的類添加一些方法而不用子類化該類,也不需要你瞭解該類的實現細節。這是特別有用的,因為你可以給一個內建的對象添加方法。當你想在你的應用程式裡面給所有NSString類型的執行個體添加一個方法,你只需要添加一個類目,而不需要通過定義一個子類來添加該方法。比如,我想給NSString添加一個方法以判斷它是不是一個URL,寫法就像這樣:
#import <Cocoa/Cocoa.h>
@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 (@"string1is a URL");
if ( [string2 isURL] )
NSLog (@"string2is a URL");
與子類不同,你不能通過類目來添加執行個體變數。但是你能通過類目重寫(override)類中已經存在的方法,當然,重寫的時候要特別小心。記住,當你通過類目更改一個類的時候,這個更改會影響你這個應用程式中所有這個類的執行個體。
總結
本文介紹了Objective-C基本知識。就像你看到的一樣,這個語言學習起來非常簡單。它沒有很多特殊的文法,並且許多規範也在Cocoa中被多次使用。如果你喜歡上面那些例子,你可以下載後面的工程然後察看它的原始碼。
LearnObjectiveC Xcode 3.0Project (56k)
http://cocoadevcentral.com/downloads/LearnObjectiveC-20080414a.zip