高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

OOP in Objective-C

這回來我們來看看在Objective-C裡的物件導向程式(Object-Oriented Programming)的東西,不過有些像是”何謂物件導向”以及”物件導向的優缺點”之類的基礎觀念就不特別提了,網路上應該可以找到更多更詳細的參考資料,我假設你曾經在別的程式語言有寫過OOP,大概知道繼承、封裝是怎麼回事。

在別的程式語言裡,會用關鍵字class來定義類別,在Objective-C的話則是用@interface這個關鍵字來定義。不要搞混這個@interface跟其它程式語言的interface,它們講的是不同的東西(其它程式語言的interface在Objective-C裡比較像是Protocol,之後會再做說明)。為了讓環境單純一點,我們直接開一個Command Line Tool的專案來練習手感:

image

下一步設定專案名稱,我取名為OOPTest。再來我們要來新增個類別,在Source資料夾上按右鍵,新增檔案:

image

選擇一個Objective-C Class,Subclass的地方選擇NSObject:

image

再來因為我要來寫個Book的類別,所以我就把它取名為Book.m,這邊要注意的是,這裡有個”Also create Book.h”也把它勾起來,它會一起幫忙產生header檔:

image

要特別說明的是,在Java/AS3裡,類別名稱跟檔案的名稱(*.java、*.as)是要同名的,但在Objective-C裡卻沒這樣的強制規定,不過為了方便維護起見,我會習慣把類別跟檔名取一樣的名字。另外這邊的”.m”跟”.h”檔,.h檔是它的”h”eader定義檔(放@interface的地方),.m則是它的i”m”plementation實作檔(放@implementation的地方),其實你要放在同一個檔裡,甚至是全部直接寫在main裡也ok,但為了好維護,通常我們會另外把它拆成兩個檔案來寫。接著我們就要來Book.h裡放類別的定義:

1
2
3
4
5
6
7
8
9
10
@interface Book : NSObject
{
  int price;
}

-(int) price;
-(void) setPrice: (int) p;
+(void) printBookInfo;

@end

其實這個類別定義,用翻譯成一般話就是說「我定義了一個叫做Book的類別,在這個類別裡有一個price的整數變數,並寫了一對的getter跟setter,還有一個叫做printBookInfo的class method」。為什麼需要getter跟setter? 在 Objective-C 裡的實體變數(instance variable,可簡稱為ivar)預設是 protected 的,所以你沒辦法從外部直接存取。當然也是可以透過 @public 語法來設定成公開的屬性,但這並不是好的 OOP 實作方法。

這裡有幾個比較陌生的字,NSObject是Objective-C裡最上層的類別,所有的物件都是繼承自它。要繼承的話,只要用一個冒號”:”即可,後面放的是要繼承的父類別名稱。有注意到前面的加號(+)跟減號(-)了嗎? 它也是有特別意義的,加號代表這個方法是屬於類別方法(class method),減號則表示這個方法是實體方法(instance method)。有什麼不同? class method是針對類別本身呼叫的,不需要產生instance即可使用,像這樣:

1
[Book printBookInfo];

而instance method則需要先由類別產生物件,再由物件來呼叫:

1
2
3
Book *book = [[Book alloc] init];
[book setPrice: 20];
NSLog(@"the price of the book is %i", [book price]);

在Java/AS3裡,類別的定義跟實作是寫在一起的,但在Objective-C裡則是分開寫的。到目前為止,我們只用了@interface只是”定義”了類別的骨頭,但還沒實際把肉填進去,要填肉的話,用的是@implementation語法,接著切換到Book.m(你可以用command + option + 上鍵來切換):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Book.h"

@implementation Book
-(int) price
{
  return price;
}

-(void) setPrice: (int) p
{
  price = p;
}

+(void) printBookInfo
{
  NSLog(@"Hello, This is a book");
}

@end

記得我們在@interface裡有定義了2個instance method跟1個class method,在@implementation就要把它實作出來。補充一下,這裡的#import跟在c/c++裡出現的#include有類似的用途,都是把東西給匯進來使用,但這兩個語法最大的差別是#import只會匯入一次,所以你可能會在c/c++的程式碼裡常看到#ifdef之類的用法來做檢查,但如果用#import就不用擔心這問題了。

最後回來我們整個程式的進入點,在這個範例裡是OOPTest.m(如果你的專案取名字跟我的不同,這個檔名也會不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import <Foundation/Foundation.h>
#import "Book.h"

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  // 新增一個Book實體
  Book *b = [[Book alloc] init];

  // 設定價錢
  [b setPrice:20];

  // 把價錢印出來
  NSLog(@"the price of the book is %i", [b price]);

  // 用完記得放掉
  [b release];

  // 這是類別方法
  [Book printBookInfo];

  [pool drain];
  return 0;
}

按下”Build and Run”之後的執行結果:

the price of the book is 20
Hello, This is a book

如果你的程式在編譯過程有發生錯誤而沒辦法正常的執行,那可能是有程式碼打錯字,再回頭檢查看看,常見的錯誤是大小寫打錯,還有每行程式碼最後面記得要加分號。

你也許會好奇,Book哪裡來的allocinit,還有release方法? 因為這些方法都是繼承來的,不用特別寫就有了。

不過每次為了某個變數就要寫一對getter/setter應該覺得很煩人吧,Objective-C有提供了@property@synthesize語法,之後會再特別說明相關用法。另外,Objective-C是單一繼承的,可以靠Protocol以及Category來補足這部份的不足。下一篇,我們會來看看@property跟@synthesize是怎麼回事。

建議閱讀:

Comments