Objective-C基礎文法快速入門
2010-11-04 16:32 折酷吧 zheku8 字号:
T|
假如我們對面向對象的思維已經C語言都很熟悉的話,對于我們學習Objective-C将會非常有用。假如我們對C語言還不熟悉的話,那我們需要學習一下C語言。
AD:
2010年11月程式設計語言排行榜和
2月程式設計語言排行榜講的都是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
在Objective-C 2.0裡面,新增加了一個"."操作的文法。在Mac OS X 10.5裡面就使用了Objective-C 2.0文法:
- photo.caption = @"Day at the Beach";
- output = photo.caption;
我們兩種方式都可以使用。但是在一個工程裡面最好保持風格一緻,隻使用某一種。"."操作隻能夠被使用在setters和getters裡面,而不能用在一般意思的方法上。
建立對象
主要有兩種方式來建立一個對象。第一種辦法像這面這樣:
這是一種非常習慣性的風格。在這種方式情況下,我們建立的是系統自動釋放(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:
- - caption;
- - photographer;
别忘記,Objective-C方法不需要加get字首。一個單獨小橫杆表明它是一個執行個體的方法。假如是一個加号的話,那就說明它是一個類的方法。
編譯器預設的方法的傳回類型為id。還有所有的方法的參數的預設類型也都是id類型的。是以上面的代碼從技術上講是對的。但是很少這麼用。我們還是給它加上傳回類型吧:
- - (NSString*) caption;
- - (NSString*) photographer;
下面我們再加上setters:
- - (void) setCaption: (NSString*)input;
- - (void) setPhotographer: (NSString*)input;
Setters不需要傳回任何值,是以我們把它的類型指定為void.
類的實作
我們通過實作getters來建立一個類的實作:
- #import "Photo.h"
- @implementation Photo
- - (NSString*) caption {
- return caption;
- - (NSString*) photographer {
- return photographer;
這部分的代碼由@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];
- [totalAmount release];
這裡是本地引用的例子。我們隻需要release我們用alloc建立的對象:
- NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
- NSNumber* value2 = [NSNumber numberWithFloat:14.78];
- // only release value1, not value2
- [value1 release];
這裡是用本地引用對象去設一個成員變量的例子:
- [self setTotal:value1];
- [self setTotal:value2];
注意到如何管理本地引用其實都是一樣的。不管你是否把它設給了一個成員變量。我們無須考慮setters的内部實作。
如果我們很好的了解了這些的話,我們基本上了解了80%的Objective-C記憶體管理方面的内容了。
屬性Properties
前面我們寫caption和author的accessors的時候,你可以已經注意到了代碼非常簡明,應該可以被抽象提取出來。
屬性在Objective-C裡是一個新的功能。他可以讓我們自動的生成accessors,另外還有一些别的優點。我們可以把上面Photo的類轉成用屬性來實作:
上面那個類原先的實作是這樣:
假如用屬性來實作就是這樣:
- @property (retain) NSString* caption;
- @property (retain) NSString* photographer;
@property是Objective-C來聲明屬性的編譯指令。括号裡面的"retain"指明了setter需要retain輸入的對象。這行其他的部分指定了屬性的類型以及名字。
下面讓我們來看看這個類的實作:
- @synthesize caption;
- @synthesize photographer;
@synthesize指令自動的生成了我們的setters和getters。是以我們隻需要實作類的dealloc方法。
Accessors隻有當他們原先沒有的時候,才會被生成。是以可以放心大膽的去用@synthesize來指定屬性。而且可以随意實作你自己的getter和setter。編譯器會自己去找哪個方法沒有。
屬性聲明還有别的選項,但是限于篇幅層次,我們下次再介紹。
Logging
在Objective-C裡,往console寫日記非常簡單。事實上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函數實作看上去更好一些:
- self.caption = nil;
- self.photographer = nil;
之是以可以這麼做是因為我們給把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自帶的類的方法,這非常有效。如果我們想在我們程式工程的NSString能夠增加一個方法,我們就可以使用category。甚至都不需要自己實作一個NSString的子類。
比如,我們想在NSString裡面增加一個方法來判斷它是否是一個URL,那我們就可以這麼做:
- @interface NSString (Utilities)
- - (BOOL) isURL;
這跟類的定義非常類似。差別就是category沒有父類,而且在括号裡面要有category的名字。名字可以随便取,但是習慣叫法會讓人比較明白category裡面有些什麼功能的方法。
這裡是具體的實作。但是要注意,這本身并不是一個判斷URL很好的實作。我們主要是為了整體的了解category的概念。
- #import "NSString-Utilities.h"
- @implementation NSString (Utilities)
- - (BOOL) isURL
- if ( [self hasPrefix:@"http://"] )
- return YES;
- else
- return NO;
現在我們可以在任何的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裡面被反複運用。
【責任編輯:
立方TEL:(010)68476606】