開始吧
下載這篇教學
設定環境
為了編譯 GNUstep 應用程式,必須先執行位於 /usr/GNUstep/System/Makefiles/GNUstep.sh 的 GNUstep.sh 這個檔案。這個路徑取決於你的系統環境,有些是在 /usr, some /usr/lib,有些是 /usr/local。如果你的 shell 是以 csh/tcsh 為基礎的 shell,則應該改用 GNUStep.csh。建議把這個指令放在 .bashrc 或 .cshrc 中。
前言
(譯注:台灣出版書名為 C 程式語言第二版)這是 C 語言的設計者所寫的書。
編譯 hello world
所有這篇初學者指南的原始碼都可以由
objc.tar.gz 下載。這篇教學中的許多範例都是由 Steve Kochan 在
Programming in Objective-C
. 一書中撰寫。如果你想得到更多詳細資訊及範例,請直接參考該書。這個網站上登載的所有範例皆經過他的允許,所以請勿複製轉載。
創建 classes
@interface
@implementation
把它們湊在一起
基於 "Programming in Objective-C," Copyright © 2004 by Sams Publishing 一書中的範例,並經過允許而刊載。 Fraction.h
#import
@interface Fraction: NSObject {
int numerator;
int denominator;
}
-(void) print;
-(void) setNumerator: (int) d;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end
NSObject:NeXTStep Object 的縮寫。因為它已經改名為 OpenStep,所以這在今天已經不是那麼有意義了。 繼承(inheritance)以 Class: Parent 表示,就像上面的 Fraction: NSObject。 夾在 @interface Class: Parent { .... } 中的稱為 instance variables。 沒有設定存取權限(protected, public, private)時,預設的存取權限為 protected。設定權限的方式將在稍後說明。 Instance methods 跟在成員變數(即 instance variables)後。格式為:scope (returnType) methodName: (parameter1Type) parameter1Name; scope 有class 或 instance 兩種。instance methods 以
- 開頭,class level methods 以
+ 開頭。 Interface 以一個 @end 作為結束。
詳細說明...
多重參數
建構子(Constructors)
存取權限
Class level access
異常情況(Exceptions)
目前為止我還沒展示如何傳遞多個參數。這個語法乍看之下不是很直覺,不過它卻是來自一個十分受歡迎的 Smalltalk 版本。
基於 "Programming in Objective-C," Copyright © 2004 by Sams Publishing一書中的範例,並經過允許而刊載。 Fraction.h
...
-(void) setNumerator: (int) n andDenominator: (int) d;
...
Fraction.m
...
-(void) setNumerator: (int) n andDenominator: (int) d {
numerator = n;
denominator = d;
}
...
main.m
#import
#import "Fraction.h"
int main( int argc, const char *argv[] ) {
// create a new instance
Fraction *frac = [[Fraction alloc] init];
Fraction *frac2 = [[Fraction alloc] init];
// set the values
[frac setNumerator: 1];
[frac setDenominator: 3];
// combined set
[frac2 setNumerator: 1 andDenominator: 5];
// print it
printf( "The fraction is: " );
[frac print];
printf( "/n" );
// print it
printf( "Fraction 2 is: " );
[frac2 print];
printf( "/n" );
// free memory
[frac release];
[frac2 release];
return 0;
}
output
The fraction is: 1/3
Fraction 2 is: 1/5
這個 method 實際上叫做 setNumerator:andDenominator: 加入其他參數的方法就跟加入第二個時一樣,即 method:label1:label2:label3: ,而呼叫的方法是 [obj method: param1 label1: param2 label2: param3 label3: param4] Labels 是非必要的,所以可以有一個像這樣的 method:method:::,簡單的省略 label 名稱,但以 : 區隔參數。並不建議這樣使用。
繼承、多型(Inheritance, Polymorphism)以及其他物件導向功能
id 型別
繼承(Inheritance)
動態識別(Dynamic types)
Categories
Posing
Protocols
Objective-C 有種叫做 id 的型別,它的運作有時候像是 void*,不過它卻嚴格規定只能用在物件。Objective-C 與 Java 跟 C++ 不一樣,你在呼叫一個物件的 method 時,並不需要知道這個物件的型別。當然這個 method 一定要存在,這稱為 Objective-C 的訊息傳遞。
基於 "Programming in Objective-C," Copyright © 2004 by Sams Publishing一書中的範例,並經過允許而刊載。 Fraction.h
#import
@interface Fraction: NSObject {
int numerator;
int denominator;
}
-(Fraction*) initWithNumerator: (int) n denominator: (int) d;
-(void) print;
-(void) setNumerator: (int) d;
-(void) setDenominator: (int) d;
-(void) setNumerator: (int) n andDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end
Fraction.m
#import "Fraction.h"
#import
@implementation Fraction
-(Fraction*) initWithNumerator: (int) n denominator: (int) d {
self = [super init];
if ( self ) {
[self setNumerator: n andDenominator: d];
}
return self;
}
-(void) print {
printf( "%i / %i", numerator, denominator );
}
-(void) setNumerator: (int) n {
numerator = n;
}
-(void) setDenominator: (int) d {
denominator = d;
}
-(void) setNumerator: (int) n andDenominator: (int) d {
numerator = n;
denominator = d;
}
-(int) denominator {
return denominator;
}
-(int) numerator {
return numerator;
}
@end
Complex.h
#import
@interface Complex: NSObject {
double real;
double imaginary;
}
-(Complex*) initWithReal: (double) r andImaginary: (double) i;
-(void) setReal: (double) r;
-(void) setImaginary: (double) i;
-(void) setReal: (double) r andImaginary: (double) i;
-(double) real;
-(double) imaginary;
-(void) print;
@end
Complex.m
#import "Complex.h"
#import
@implementation Complex
-(Complex*) initWithReal: (double) r andImaginary: (double) i {
self = [super init];
if ( self ) {
[self setReal: r andImaginary: i];
}
return self;
}
-(void) setReal: (double) r {
real = r;
}
-(void) setImaginary: (double) i {
imaginary = i;
}
-(void) setReal: (double) r andImaginary: (double) i {
real = r;
imaginary = i;
}
-(double) real {
return real;
}
-(double) imaginary {
return imaginary;
}
-(void) print {
printf( "%_f + %_fi", real, imaginary );
}
@end
main.m
#import
#import "Fraction.h"
#import "Complex.h"
int main( int argc, const char *argv[] ) {
// create a new instance
Fraction *frac = [[Fraction alloc] initWithNumerator: 1 denominator: 10];
Complex *comp = [[Complex alloc] initWithReal: 10 andImaginary: 15];
id number;
// print fraction
number = frac;
printf( "The fraction is: " );
[number print];
printf( "/n" );
// print complex
number = comp;
printf( "The complex number is: " );
[number print];
printf( "/n" );
// free memory
[frac release];
[comp release];
return 0;
}
output
The fraction is: 1 / 10
The complex number is: 10.000000 + 15.000000i
這種動態連結有顯而易見的好處。你不需要知道你呼叫 method 的那個東西是什麼型別,如果這個物件對這個訊息有反應,那就會喚起這個 method。這也不會牽涉到一堆繁瑣的轉型動作,比如在 Java 裡呼叫一個整數物件的 .intValue() 就得先轉型,然後才能呼叫這個 method。
記憶體管理
到目前為止我都刻意避開 Objective-C 的記憶體管理議題。你可以呼叫物件上的 dealloc,但是若物件裡包含其他物件的指標的話,要怎麼辦呢?要釋放那些物件所佔據的記憶體也是一個必須關注的問題。當你使用 Foundation framework 建立 classes 時,它如何管理記憶體?這些稍後我們都會解釋。 注意:之前所有的範例都有正確的記憶體管理,以免你混淆。
Retain and Release(保留與釋放)
Dealloc
Autorelease Pool
Retain 以及 release 是兩個繼承自 NSObject 的物件都會有的 methods。每個物件都有一個內部計數器,可以用來追蹤物件的 reference 個數。如果物件有 3 個 reference 時,不需要 dealloc 自己。但是如果計數器值到達 0 時,物件就得 dealloc 自己。[object retain] 會將計數器值加 1(值從 1 開始),[object release] 則將計數器值減 1。如果呼叫 [object release] 導致計數器到達 0,就會自動 dealloc。 Fraction.m
...
-(void) dealloc {
printf( "Deallocing fraction/n" );
[super dealloc];
}
...
基於 "Programming in Objective-C," Copyright © 2004 by Sams Publishing一書中的範例,並經過允許而刊載。 main.m
#import "Fraction.h"
#import
int main( int argc, const char *argv[] ) {
Fraction *frac1 = [[Fraction alloc] init];
Fraction *frac2 = [[Fraction alloc] init];
// print current counts
printf( "Fraction 1 retain count: %i/n", [frac1 retainCount] );
printf( "Fraction 2 retain count: %i/n", [frac2 retainCount] );
// increment them
[frac1 retain]; // 2
[frac1 retain]; // 3
[frac2 retain]; // 2
// print current counts
printf( "Fraction 1 retain count: %i/n", [frac1 retainCount] );
printf( "Fraction 2 retain count: %i/n", [frac2 retainCount] );
// decrement
[frac1 release]; // 2
[frac2 release]; // 1
// print current counts
printf( "Fraction 1 retain count: %i/n", [frac1 retainCount] );
printf( "Fraction 2 retain count: %i/n", [frac2 retainCount] );
// release them until they dealloc themselves
[frac1 release]; // 1
[frac1 release]; // 0
[frac2 release]; // 0
}
output
Fraction 1 retain count: 1
Fraction 2 retain count: 1
Fraction 1 retain count: 3
Fraction 2 retain count: 2
Fraction 1 retain count: 2
Fraction 2 retain count: 1
Deallocing fraction
Deallocing fraction
Retain call 增加計數器值,而 release call 減少它。你可以呼叫 [obj retainCount] 來取得計數器的 int 值。 當當 retainCount 到達 0,兩個物件都會 dealloc 自己,所以可以看到印出了兩個 "Deallocing fraction"。
Foundation framework classes
Foundation framework 地位如同 C++ 的 Standard Template Library。不過 Objective-C 是真正的動態識別語言(dynamic types),所以不需要像 C++ 那樣肥得可怕的樣版(templates)。這個 framework 包含了物件組、網路、執行緒,還有更多好東西。
NSArray
NSDictionary
基於 "Programming in Objective-C," Copyright © 2004 by Sams Publishing一書中的範例,並經過允許而刊載。 main.m
#import
#import
#import
#import
#import
void print( NSArray *array ) {
NSEnumerator *enumerator = [array objectEnumerator];
id obj;
while ( obj = [enumerator nextObject] ) {
printf( "%s/n", [[obj description] cString] );
}
}
int main( int argc, const char *argv[] ) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *arr = [[NSArray alloc] initWithObjects:
@"Me", @"Myself", @"I", nil];
NSMutableArray *mutable = [[NSMutableArray alloc] init];
// enumerate over items
printf( "----static array/n" );
print( arr );
// add stuff
[mutable addObject: @"One"];
[mutable addObject: @"Two"];
[mutable addObjectsFromArray: arr];
[mutable addObject: @"Three"];
// print em
printf( "----mutable array/n" );
print( mutable );
// sort then print
printf( "----sorted mutable array/n" );
[mutable sortUsingSelector: @selector( caseInsensitiveCompare: )];
print( mutable );
// free memory
[arr release];
[mutable release];
[pool release];
return 0;
}
output
----static array
Me
Myself
I
----mutable array
One
Two
Me
Myself
I
Three
----sorted mutable array
I
Me
Myself
One
Three
Two
陣列有兩種(通常是 Foundation classes 中最資料導向的部分),NSArray 跟 NSMutableArray,顧名思義,mutable(善變的)表示可以被改變,而 NSArray 則不行。這表示你可以製造一個 NSArray 但卻不能改變它的長度。 你可以用 Obj, Obj, Obj, ..., nil 為參數呼叫建構子來初始化一個陣列,其中 nil 表示結尾符號。 排序(sorting)展示如何用 selector 來排序一個物件,這個 selector 告訴陣列用 NSString 的忽略大小寫順序來排序。如果你的物件有好幾個排序方法,你可以使用這個 selector 來選擇你想用的方法。 在 print method 裡,我使用了 description method。它就像 Java 的 toString,會回傳物件的 NSString 表示法。 NSEnumerator 很像 Java 的列舉系統。while ( obj = [array objectEnumerator] ) 行得通的理由是 objectEnumerator 會回傳最後一個物件的 nil。在 C 裡 nil 通常代表 0,也就是 false。改用 ( ( obj = [array objectEnumerator] ) != nil ) 也許更好。