原文地址:http://blog.chinaunix.net/uid-20622737-id-2151722.html
Objective-C 的語法與Cocoa 框架
//--------------------------------------------------------------------------------
c++ ,obj-c 代碼對比:(可以參考一下)
// C++ 類的聲明
class ClassA: public BaseClass
{
public :
int number1;
int number2;
public:
int setNumber(int _num1, int _num2);
};
// C++ 類的實現
int ClassA::setNumber(int _num1, int _num2)
{
number1 = _num1;
number2 = _num2;
}
// obj-c 中類的聲明
@interface ClassA: NSObject
{
@public
int number1;
int number2;
}
-(int)setNumber:(int)_numb1 Number2:(int) _num2;
@end
// obj-c 中類的實現
@implementation ClassA
-(int)setNumber:(int)_numb1 Number2:(int) _num2
{
number1 = _num1;
number2 = _num2;
}
@end
//--------------------------------------------------------------------------------
李海峯 QQ:61673110 郵箱:[email protected]
Objective-C 是蘋果Mac OS X、iOS 平臺的開發語言,Objective-C 基於C 語言的,增加面向對
象的相關特性。你可以認爲Objective-C 就是另一個版本的C++,也就是它採用了與C++不同
的語法,但也實現了面向對象。
NextStep 是一個使用Objective-C 語言編寫的功能強大的工具包,裏面有大量的類庫、結構
體等,被蘋果收購之後,更名爲Cocoa,但是蘋果並未更改NextStep 中的類庫名稱,因此你
會看到大量的以NS 爲前綴的類名、結構體、枚舉等。在Objective-C 中使用前綴可以有效的
防止名稱衝突。
Cocoa 框架由Foundation Kit、App Kit 兩部分組成,前者是基礎工具庫,是你必須首先要學
會的,後者主要是UI 庫、高級對象等,我們這裏只介紹Foundation Kit。
本文檔使用Windows 上的GNUStep 作爲Objective-C 的編譯器,不支持Objective-C 2.0 的相
關新特性,但基本完全支持Cocoa 的Foundation Kit、App Kit 工具庫。
1. GNUStep 的安裝:
首先前往網址http://www.gnustep.org/experience/Windows.html,下載文件:
然後按照下面的順序安裝這四個文件到同一個目錄(例如:C:\GNUstep):
(1.)gnustep-msys-system-xxx.exe
(2.)gnustep-core-xxx.exe
(3.)gnustep-devel-xxx.exe
(4.)gnustep-cairo-xxx.exe
安裝完成後,進入開始---程序---GNUStep---Shell,你會看到一個在Windows 上打開的命令行
窗口,你可以在其中使用Linux 的Shell 命令cd、ls、rm 等進行操作。啓動Shell 之後,它會
在GNUStep 的目錄中建一個/home/xxx/的文件夾,xxx 爲你當前登陸Windows 系統的用戶名
稱,Shell 默認進入的就是這個目錄,也就是Linux 上的cd ~。
你可以在Shell 中使用vi 命令創建Objective-C 的源文件,但是推薦的方式是使用UltraEdit
等編輯器編輯Objective-C 的源文件,然後在Shell 中編譯、運行。
GNUStep 使用GCC 編譯器,編譯Objective-C 的命令:
gcc -o hello.exe hello.m
-I/GNUstep/System/Library/Headers
-fconstant-string-class=NSConstantString
-L/GNUstep/System/Library/Libraries
-lobjc -lgnustep-base
(1.)紅色部分爲編譯生成的可運行文件,藍色部分爲要編譯的源文件,可以有多個,使用空
格分隔。
(2.) 參數-I 表示頭文件查找的路徑,-L 表示庫文件查找路徑,-l 表示需要鏈接的庫文件,
-fconstant-string-class=NSConstantString 主要是指定常量字符串所使用的class。
2. 類定義:
我們定義一個類,這個類完成的功能是使用兩個int 類型的數字組成一個分數。在Objective-C
中必須首先定義一個接口,該接口用於描述這個類的組成,包含成員變量、類變量、類方法、
成員方法。接口文件的擴展名爲h,也就是定義爲C 語言中的頭文件。
Fraction.h
#import <Foundation/Foundation.h>
static int t=0;
@interface Fraction: NSObject{
int numerator;//分子
@public int denominator;//分母
}
-(void) setNumerator: (int) numerator;//numerator 的setter 方法
-(void) setDenominator: (int) denominator;//denominator 的setter 方法
-(void) setNumerator: (int) numerator andDenominator: (int) denominator;
//一個同時設置兩個成員變量的快捷方法
-(int) numerator;//numerator 的getter 方法
-(int) denominator;//denominator 的getter 方法
-(void) print;
+(void) t;
@end
一個Objective-C 中的接口就是C 語言中的一個header 文件,這個文件結構如下所示:
#import Header
static 類型 變量名; // 只能在本類中訪問 (static 變量只能在本文件中訪問)
@interface 接口名: 父類名稱{
訪問修飾符 類型 變量名; // 類成員變量
… …
}
-(返回值類型) 方法名: (參數類型) 參數名 標籤1: (參數類型) 參數名 … … // 成員方法
+(返回值類型) 方法名: (參數類型) 參數名 標籤1: (參數類型) 參數名 … … // 類方法
@end
我們來逐行看一下上面的內容:
(1.) 這裏與C 語言不同的是導入頭文件使用的是import,而不是include。另外與C 語言一樣
的地方是如果你想從當前目錄查找Header 文件,找不到就到系統的頭文件庫中查找,
請使用#import “Header 文件”,如果你只想從系統的頭文件庫中查找,請使用#import
<Header 文件>。Foundation/Foundation.h 包含了Foundation Kit 中的所有的頭文件定義,
GNUStep 的Objective-C 的Foundation 頭文件在
\GNUStep 安裝目錄\GNUstep\System\Library\Headers\Foundation 文件夾。
GNUStep 的Objective-C 的AppKit 頭文件在
\GNUStep 安裝目錄\GNUstep\System\Library\Headers\ AppKit 文件夾。
(2.) static 標識的類變量定義在接口的外面,類變量只能本類訪問,除非提供類方法給外部
訪問這個類變量。
(3.) Objective-C 中的@+指令表示C 語言之外的Objective-C 的衍生語法,因此@interface 表示
定義了一個接口,接口名稱之後緊跟了一個冒號,冒號後是父類的名字,Objective-C 中
的頂級父類是NSObject。
(4.) 接口定義之後緊接着一對{ },其中定義了成員變量,所謂的成員變量就相當於JAVA 中的
實例變量,從屬於類的對象。Objective-C 中的成員變量使用@public、@protected、@private
作爲訪問修飾符,默認是@protected。這裏你要知道的是Objective-C 中只有成員變量有
訪問修飾符,類變量、類方法、成員方法是沒有訪問修飾符的,所有的方法都是public
的,所有的類變量都是私有的。
(5.) 以-開頭的方法爲成員方法,以+開頭的方法爲類方法,方法中的類型描述(返回值類型、
參數類型)都必須使用( )包圍。如果方法有多個參數,每個參數都有一個標籤名(可以
省略,但不建議這樣做),每個標籤名之後使用冒號與參數描述分隔。在有多個參數的
方法中,實際的方法名稱爲 方法名:標籤名1:標籤名2:… …,上面的擁有兩個參數的方
法的方法名爲setNumerator:andDenominator:。
這裏與JAVA不同的是Objective-C中的類方法只能類調用,如果你使用對象調用會報錯,
而JAVA 僅僅是在編譯期給出警告。另外,Objective-C 中的大多數類方法都被用來提
供初始化對象的便捷方法Convenience method。
(6.) 以@end 表示接口定義結束。這是因爲與JAVA 不同的是JAVA 的類型定義使用{ }包圍,
而Objective-C 中的{ }只包圍成員變量,因此必須有個結束標誌,一般JAVA 程序員經常
會忘記寫這個結束標記。
這裏你要知道Objective-C 的@interface 與JAVA 的interface 並不是一回事兒,後面你會看到
Objective-C 中的@protocol 與JAVA 中的interface 纔是等同的。這裏你只需要記住的是
Objective-C 中的@interface 只是類的一個描述,因爲@interface 通常在獨立的h 文件中,你
可以把它類比成C 語言中的函數原型,也就是在Objective-C 裏應該叫做類的原型。通過這
個原型,編譯器可以知道具體實現類有哪些功能。
上面的接口中的方法很簡單,主要就是成員變量的setter、getter 方法,這與JAVA 沒有什麼
不同的。但是你會發現getter 方法沒有以get 作爲方法名稱前綴,這是因爲get 開頭的方法
在Objective-C 中有着特殊的含義,這在後面將會看到。
下面我們編寫實現類,Objective-C 的類文件使用擴展名m。
Fraction.m
#import "Fraction.h"
@implementation Fraction
-(void) setNumerator: (int) n{
numerator=n;
}
-(void) setDenominator: (int) d{
denominator=d;
}
-(void) setNumerator: (int) n andDenominator: (int) d{
numerator=n;
denominator=d;
}
-(int) numerator{
return numerator;
}
-(int) denominator{
return denominator;
}
-(void) print{
printf("%d/%d\n",numerator,denominator);
}
-(void) m{
printf("-m:The class variable t is %d\n",++t);
}
+(void) t{
printf("+t:The class variable t is %d\n",++t);
}
@end
因爲我們將Fraction.m 與Fraction.h 放在一個文件夾下面,所以#import 使用了” ”,這個類的
任務就是實現接口中的方法,因此與接口的結構不同的地方就是,你不能在這裏定義變量,
@interface 換成了@implementation,其餘就沒有什麼特別的了。你不必在這裏實現@interface
中的全部方法,這不會導致錯誤。這裏有個-(void) m 方法比較特別,我們並沒有在@interface
中聲明,那麼這個方法可以調用嗎?因爲Objective-C 是動態語言,即便是@interface 中沒有
定義的方法,依然可以被調用。
另外,你需要注意的是setter 方法與接口中不同的是參數名縮寫成了n、d,這是因爲在方
法中,本地變量(參數、方法中定義的變量)在名稱衝突的情況下,會隱藏成員變量,因此
導致numerator=numerator 變成了無意義的操作。當然你可以使用後面提到的self 關鍵
字,寫成 self->numerator=numerator,也就是JAVA 中的常用的this.x=x 的寫法。
下面我們編寫調用代碼,因爲Objective-C 基於C 語言,所以程序的入口依然是main 函數。
這裏注意#import 的是h,不是m。
main.m
#import "Fraction.h"
int main(int argc,const char *argv[])
{
Fraction *frac=[[Fraction alloc] init];
[frac setNumerator: 3 andDenominator: 5];
[frac print];
printf("The denominator of Fraction is %d\n",frac->denominator);
[Fraction t]; //調用類方法
[frac m];
[frac release];
return 0;
}
(1.) 第一行我們創建了Fraction 的實例(對象),Objective-C 中實例只能使用指針作爲變量,
而不能使用值,所以你看到了*frac,而不是frac,這與JAVA 是一致的,JAVA 中的指向
實例的變量(JAVA 中叫做引用)也是指針,只不過JAVA 中沒有指針的概念,所以你沒
有看到*。至於等號右側的創建實例的代碼,你可以在下面看到,這裏先不用理會。
(2.) 第二行代碼調用同時設置兩個變量的方法,我們看到Objective-C 的調用方法的語法格式
爲[類或者實例的指針 方法名: 參數1 標籤1: 參數2… …]。這種調用格式被稱爲中
綴語法,初次看起來有點兒怪,但實際這樣更加有效。舉個例子,你接手了一個離職的
人程序,其中的JAVA 程序調用了一個有五個甚至更多的參數的方法,但是你手裏沒有
這個方法的API,那麼你很難猜得出來這五個參數到底都幹什麼用的,但是Objective-C
調用的時候,每個參數前面都必須有方法的標籤名,這樣你便能很容易的從標籤名看出
這個參數是什麼意思。
(3.) 第四行在C 的printf()函數中使用了對象->成員變量的語法訪問實例的變量,但一般我
們不推薦這麼做,而是使用getter 方法。這裏你不能訪問numerator 變量,因爲它是
@protected 的,只能本類、子類直接訪問。
(4.) 第五行我們調用了類方法t,你也可以換成這樣的寫法:
[[Fraction class] t];
或者
Class clazz=[Fraction class];
[clazz t];
class 來自於NSObject,相當於JAVA 中的getClass()方法,也就是獲取這個類的Class 對象,
clazz 前面沒有*,這是因爲Class 已經是一個指針。另外這種嵌套調用的方式,你也要習
慣,這就和JAVA 中的A.b().c()沒有什麼區別。
獲取Class 有如下幾種方法:
[類或者對象 class]
[類或者對象 superclasss]
NSClassFromString(類名的字符串形式)
你也可以通過如下的函數把Class 轉換爲字符串形式:
NSStringFromClass(Class)
(5.) 第六行我們調用了m 方法,這個方法你會發現並沒有在@interface 中聲明,這裏依然調
用了,只是在編譯的時候收到一個警告。這就是前面所有的Objective-C 是動態語言的原
因。但是一般情況下,你都會給別人提供h 文件,所以你在m 文件中寫的h 文件中沒
有的方法,別人也是不會知道的,這個方法相當於變相的私有化了。
(6.) 第七行我們釋放了frac 實例在第一行alloc 所申請的內存空間,Objective-C 的內存管理
後面會看到。另外,你會發現Fraction.h 中沒有定義alloc、init、release 方法,但是我們
上面調用了,很顯然,這些方法來自於父類NSObject。
編譯、運行上面的程序,你會看到Shell 窗口輸出如下內容:
3. Objective-C 中的布爾類型:
早期的C 語言中是沒有布爾類型的(C99 增加了布爾類型),Objective-C 中增加BOOL 類型
來表示YES、NO,注意不是TRUE、FALSE。
BOOL 使用了一個8 位(一個字節)的整數進行表示,8 位全0 就是NO。
我們知道C 語言中非0 值即爲邏輯真,因此常常會有int i=5;while(i){… …}的寫法。在
Objective-C 中一定要注意慎用C 語言中的這種數字與邏輯真假混合對待的做法去操作BOOL
類型變量。例如:
BOOL bi=8960;
if(bi==YES)
{
printf("YES");
}
這裏會輸出YES 嗎?不會的。爲什麼呢?8960 是非0 值,它不是邏輯真嗎?還記得上面說
過BOOL 是一個8 位的整數嗎?
因爲 8960 用二進制表示是大於8 位的,也就是說高位無效,只保留8960 的低八位,8960 的低八位恰好全都是0,因此bi 就是NO 了。
因此在Objective-C中一定要注意這個問題,非零值未必是BOOL 的YES,但是0 一定是NO。
所以有C 語言編程經驗的,最好不要把BOOL 與整數摻合在一起作爲布爾類型的判斷,可能
C 語言的開發者認爲直接用數字作爲布爾值進行判斷在寫法上更爲簡潔。
4. Objective-C 中的null:
Objective-C 中的對象使用nil 表示null,但是它與null 是有區別的,如果你向null 發送消息
(使用null 調用方法)會得到運行時錯誤,這在JAVA 中司空見慣的空指針異常就已經知道
了。但是nil 是可以迴應消息(respond to message),因此你不必再爲空指針而煩惱。
5. 與C 混合編寫:
// 混合編寫,文件名的後綴必須寫成 .mm 的格式,要不編譯報錯
我們看一段代碼:
BOOL differentInt(int m , int n)
{
if(m!=n)
return YES;
else
return NO;
}
NSString *boolString(BOOL yn){
if(yn==YES){
return @"YES";
}
else{
return @"No";
}
}
int main(int argc,const char *argv[]){
NSLog(boolString(differentInt(5,3)));
return 0;
}
這裏我們定義了函數differentInt()用於比較兩個整數是否相等,boolString()函數用於將BOOL
類型轉換爲字符串。這兩個函數的返回值都是Objective-C 中的類型,其中的BOOL 不是對象
類型,所以不使用指針,因此方法名稱前面沒有*,NSString 是Objective-C 中的字符串對象,
相當於JAVA 中的String 類型,@”… …”是一種NSString 的字面值的表示方法,與JAVA 中的”… …”
可以直接表示字符串,而不必非得顯示的用String 來引用字符串是一樣的。這裏注意與
Objective-C 的類型中的方法定義不同的是,函數的返回值如果是指針,*寫在C 語言的函數
名稱前面,而不是返回值的前面。
在main 函數中我們使用了Objective-C 的函數NSLog(@”格式化字符串”,變量1,變量2,… …),
這與C 語言的printf(”格式化字符串”,變量1,變量2,… …)很相似,不過它會像JAVA 中的LOG4J
一樣,在輸出語句之前增加日期戳、運行的類名、自動追加換行符\n 等信息。
你可能又會問Objective-C 不都是對象嗎?怎麼還出來個NSLog()的函數呢?函數不是C 語言
面向過程編程的東西嗎?其實Cocoa 中有很多的東西都不是對象,而是C 語言的函數、結構
體等。例如:
struct NSRange{
NSUInteger location;
NSUInteger length;
}
結構體NSRang 表示一個範圍,location 是範圍的起始點,length 是範圍的長度。
struct NSRect{
NSPoint point;
NSSize size;
}
結構體NSRect 表示一個矩形,point 是左頂點的座標,size 是長寬。
struct NSPoint{
float x;
float y;
}
struct NSSzie{
float width;
float height;
}
NSRange 常用來做字符串處理。NSRect 常用來做圖形處理,譬如:動畫中不斷的重新繪製矩
形等。你很容易知道這是個頻繁操作的處理過程,也就是矩形要不斷繪製、擦除。假如NSRect
是一個Objective-C 類型,由於類實例化對象要在堆內存中動態分配存儲空間,這是個很消
耗資源的操作,而動畫又是頻率較高的操作,反覆的創建、銷燬對象,效率將會極其低下。
所以Cocoa 這裏將NSRect 定義爲C 語言的結構體就大大提高了運行效率。相比較Android
平臺,有人說做遊戲純靠JAVA 是不行的,JAVA 最多就是畫畫UI 和做些簡單的應用,因爲
JAVA 不具備和C 語言混合編程的能力,事事都要藉助對象,所以必然要引入C 去編寫Android
的*.SO 的圖形引擎等,以提高處理能力。
6. 對象的初始化:
JAVA 的對象創建只有一個過程,就是調用構造方法,但是Objective-C 分爲兩個步驟:分配
內存、初始化,如上面的例子中的如下語句:
Fraction *frac=[[Fraction alloc] init];
(1.) alloc 是從NSObject 繼承而來的類方法,用於給對象分配存儲空間,所有的成員變量在
此時對確定了自己的內存位置,並被賦初值,整數類型爲0,浮點數爲0.0,BOOL 爲NO,
對象類型爲nil,alloc 方法返回對象的指針。
(2.) init 是從NSObject 繼承而來的成員方法,這個方法是你在對象創建過程可以參與的方法,
因此你可以定製自己的init 方法。Objective-C 對init 方法沒有特殊的要求,就是一個普
通方法而已,只不過習慣以init 作爲方法前綴。一般init 方法都返回當前類型的指針或
者id 類型。id 類型在Objective-C 中是一個泛型對象,與前面所說的Class 對象一樣,id
類型的變量的前面不使用*,因爲id 已經是一個結構體指針,並且結構體內部通過Class
類型的isa 指向了繼承了NSObject 的對象。id 可以表示任意類型的Objective-C 對象。
下面我們定製Fraction 的初始化方法。
在Fraction.h 中增加如下兩個方法原型:
-(id) init;
-(Fraction*) initWithNumerator: (int) numerator andDenominator: (int) denominator;
Fraction.m 的實現:
-(id) init{
self=[super init];
return self;
}
-(Fraction*) initWithNumerator: (int) n andDenominator: (int) d
{
self=[self init];
if(self)
{
[self setNumerator: n andDenominator: d];
}
return self;
}
(1.) 第一個方法我們覆蓋了NSObject 中的init 初始化方法,返回值id 你也可以寫成Fraction*,
這裏只是爲了讓你知道id 可以代表任意類型。方法的實現中我們首先調用了父類的init
方法,使用了關鍵字super,這與JAVA 沒什麼不同的。另外,你還看到了關鍵字self,
它相當於JAVA 中的this,用於指向當前實例的指針。但是奇怪的是我把[super init]的返
回值賦給了self,這在JAVA 中是沒有的操作。因爲Objective-C 是動態語言,所以init 方
法很可能會返回一個不是當前類型的返回值,也就是Fraction 的init 方法是可能返回另
一個類型的(Objective-C 中的NSString 的實現代碼init 方法返回的就是NSCFString)。因
此爲了保證程序可以正常工作,你需要把父類的init 方法產生的返回值賦給self,讓self
指向父類init 方法返回的對象,然後才能開始其它操作。
(2.) 第二個方法首先調用了第一個方法,這就相當於JAVA 中的策略模式,重載方法互相調
用,這沒有什麼特別的。需要知道的是首先判斷了一下self 是否爲nil(if(slef)與
if(self!=nil)是相同的),因爲父類可能因爲某些原因導致初始化異常。
你可以在main 函數中使用新的init 方__________法:
Fraction *frac=[[Fraction alloc] initWithNumerator :2 denominator :3];
7. Objective-C 的description 方法:
JAVA中的對象都有從Object中繼承而來的String toString()方法,用於獲取對象的字符串表示,
Objective-C 中的這個方法的方法簽名爲:
-(NSString*) description;
由於這是NSObject 中的成員方法,因此我們就不必在Fraction.h 文件中聲明它了,直接在
Fraction.m 中實現如下所示:
-(NSString*) description{
return @"I am a fraction!";
}
編寫main 函數訪問:
int main(int argc,const char *argv[]){
Fraction *frac=[[Fraction alloc] initWithNumerator: 2 andDenominator: 3];
NSLog(@"%@",frac);
return 0;
}
這裏我們看到NSLog 輸出對象用的格式化字符串是%@,這樣description 方法就會被調用。
如果我們不實現自己的description,那麼會調用NSObject 的實現,輸出對象的首地址。
8. Objective-C 的異常處理:
我們的Fraction 忽略了一個問題,那就是沒有對denominator 爲0 的情況作處理,也就是分
母不能爲0。我們可以使用Objective-C 的異常機制對這個問題進行處理。
Objective-C 的異常都繼承自 N***ception ,當然 N***ception 本身也繼承自NSObject。
DenominatorNotZeroException.h
#import <Foundation/Foundation.h>
@interface DenominatorNotZeroException: N***ception
@end
DenominatorNotZeroException.m
#import "DenominatorNotZeroException.h"
@implementation DenominatorNotZeroException
@end
我們定義了分母不能爲0 的異常,Objective-C 中的異常定義起來很簡單,其實除了寫了類名
之外什麼都沒有寫。
下面我們改寫Fraction.m 的如下兩個setter 方法:
-(void) setDenominator: (int) d{
if(d==0){
N***ception *e=[DenominatorNotZeroException
exceptionWithName: @"DenominatorNotZeroException"
reason: @"The denominator is not 0!"
userInfo:nil];
@throw e;
}
denominator=d;
}
-(void) setNumerator: (int) n andDenominator: (int) d{
if(d==0){
N***ception *e=[DenominatorNotZeroException
exceptionWithName: @"DenominatorNotZeroException"
reason: @"The denominator is not 0!"
userInfo:nil];
@throw e;
}
numerator=n;
denominator=d;
}
這兩個方法中我們判斷如果denominator 爲0,則拋出DenominatorNotZeroException,
DenominatorNotZeroException 的創建使用了父類 N***ception 的類方法
exceptionWithName:reason:userInfo:,前兩個參數表示異常的名字、原因,參數類型爲
NSString,第三個參數爲用戶信息,暫不清楚做什麼的,我們傳遞nil 就可以了。異常定義
完成後使用@throw 拋出。
下面我們編寫捕獲異常的代碼:
int main(int argc,const char *argv[]){
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
@try{
Fraction *frac=[[Fraction alloc] initWithNumerator: 3 andDenominator: 0];
}@catch (DenominatorNotZeroException *dne){
printf("%s\n",[[dne reason] cString]);
}@catch (N***ception *e){
printf("%s\n",[[e name] cString]);
}@finally{
printf("finally run!");
}
[pool release];
return 0;
}
這裏的捕獲代碼基本與JAVA 的意義相同,只不過是try、catch、finally 前加@。另外,由
於 N***ception 的成員方法name、reason 返回的是NSString,因此又接着調用了NSString
的成員方法cString,得到C 的字符串類型char[],然後才能傳遞給C 的函數printf()。
這裏你還看到了一個NSAutoreleasePool,先不用理會它,因爲不寫它會報錯,後面會做講
解。
但你會發現上面的程序無法在GNUStep 中運行,提示不認識@throw,所以你只需要知道上
面的寫法就可以了,運行可以在Mac 電腦上操作。
9. id 類型:
下面我們演示一下id 類型是如何可以指向任意類型的實例的。我們定義一個複數類型
Complex。
Complex.h
#import <Foundation/Foundation.h>
@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"
@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 );//輸出複數z=a+bi
}
@end
main.m
#import "Fraction.h"
#import "Complex.h"
int main(int argc,const char *argv[]){
Fraction *frac=[[Fraction alloc] initWithNumerator: 3 andDenominator: 5];
Complex *comp=[[Complex alloc] initWithReal: 1.5 andImaginary: 3.5];
id number=frac;
[number print];
number=comp;
[comp print];
[frac release];
[comp release];
return 0;
}
我們看到id 類型number 首先指向了frac 實例,調用了它的print()方法,然後指向了comp
實例,調用了它的print()方法。所以id 你可以把它理解爲隨便,也就是它表示任意的東西。
10. 類的繼承:
下面我們以矩形、正方形的對象展示一下Objective-C 中的繼承結構。
矩形MyRectangle.h
#import <Foundation/Foundation.h>
@interface MyRectangle: NSObject{
int width;
int height;
}
-(MyRectangle*) initWithWidth: (int) weight andHeight: (int) height;
-(void) setWidth: (int) width;
-(void) setHeight: (int) height;
-(int) width;
-(int) height;
-(void) area;//計算面積
@end
矩形MyRectangle.m
#import "MyRectangle.h"
@implementation MyRectangle
-(MyRectangle*) initWithWidth: (int) w andHeight: (int) h{
self=[super init];
if(self){
[self setWidth: w];
[self setHeight: h];
}
}
-(void) setWidth: (int) w{
width=w;
}
-(void) setHeight: (int) h{
height=h;
}
-(int) width{
return width;
}
-(int) height{
return height;
}
-(void) area{
printf("%d\n",width*height);
}
@end
正方形MySquare.h
#import "MyRectangle.h"
@interface MySquare: MyRectangle{
int size;
}
-(MySquare*) initWithSize: (int) size;
-(void) setSize: (int) size;
-(int) size;
@end
這裏正方形的父類是MyRectangle。
正方形MySquare.m
#import "MySquare.h"
@implementation MySquare
-(MySquare*) initWithSize: (int) s{
self=[super init];
if(self){
[self setWidth: s];
[self setHeight: s];
//因爲正方形的長寬相等,所以我們把正方形的邊長賦給父類的長寬,以
便複用計算面積的方法area()。
}
}
-(void) setSize: (int) s{
size=s;
}
-(int) size{
return size;
}
@end
main.m
#import "MySquare.h"
int main(int argc,const char *argv[]){
MyRectangle *rec=[[MyRectangle alloc] initWithWidth: 2 andHeight: 5];
[rec area];
MySquare *squa=[[MySquare alloc] initWithSize: 6];
[squa area];//使用父類的計算面積的方法
[rec release];
[squa release];
}
11. 動態判定與選擇器:
第9 節中我們使用了id 這個泛型對象,假如別人傳遞給你一個id,你如何動態判斷這個在
運行時傳遞過來的對象到底有沒有XXX()方法呢?因爲id 可以代表任意對象,你也不知道運
行時傳遞的這個id 裏面到底有什麼東西。其實NSObject 中有一系列這樣的方法用於動態判
定,類似於JAVA 的反射機制,但是Objective-C 的用起來更爲簡單。
-(BOOL) isMemberOfClass: (Class) clazz 用於判斷對象是否是clazz 類型的實例,但不包含子類的實例。
-(BOOL) isKindOfClass: (Class) clazz 用於判斷對象是否是clazz 類型的實例或者clazz 的子類的實例。
-(BOOL) respondsToSelector: (SEL) selector 用於判斷類型或者對象是否能夠迴應某個方法,這個方法使用選擇器表示。
+(BOOL) instancesRespondToSelector:(SEL) selector 用於判斷類型所產生的實例是否能夠迴應某個方法,這個方法使用選擇器表示。
- (id) performSelector: (SEL) selector 用於動態調用類型或者對象上的一個方法。
上面的後三個方法中都涉及到了一個新類型選擇器SEL,它用於表示Objective-C 的一個方法,
我們知道在C 語言中有函數指針指向一個函數,也就是Objective-C 的選擇器就相當於C 語
言中的函數指針,只不過選擇器只能表示Objective-C 類型中定義的方法。選擇器使用
@selector(方法名)的形式獲取,例如:@selector(initWithWidth:andHeight:) 、@selector(alloc)。
同時,SEL 是在繼Class、id 之後第三個不需要在變量前使用*的類型,因爲它已經是一個指針。
另外,注意上面的紅色文字,你可以看到這兩行的方法都是-開頭的,也就是成員方法,但
是爲什麼類也可以去掉用呢?其實是類的Class 對象去掉用的,因爲Class 對象有着兩個方法
的迴應能力。
我們以第10 節的矩形、正方形的程序爲基礎,進行代碼演示。
int main(int argc,const char *argv[]){
MyRectangle *rec=[[MyRectangle alloc] initWithWidth: 2 andHeight: 5];
[rec area];
MySquare *squa=[[MySquare alloc] initWithSize: 6];
[squa area];
//-(BOOL) isMemberOfClass: (Class) clazz 用於判斷對象是否是clazz 的實例,但
不包含子類的實例。
if([squa isMemberOfClass: [MyRectangle class]]){
printf("squa isMemberOfClass MyRectangle\n");
}else{
printf("squa not isMemberOfClass MyRectangle\n");
}
if([squa isMemberOfClass: [MySquare class]]){
printf("squa isMemberOfClass MySquare\n");
}else{
printf("squa not isMemberOfClass MySquare\n");
}
printf("----------------------------------------------\n");
//-(BOOL) isKindOfClass: (Class) clazz 用於判斷對象是否是clazz 的實例或者參數
的子類的實例。
if([squa isKindOfClass: [MyRectangle class]]){
printf("squa isKindOfClass MyRectangle\n");
}else{
printf("squa not isKindOfClass MyRectangle\n");
}
if([squa isKindOfClass: [MySquare class]]){
printf("squa isKindOfClass MySquare\n");
}else{
printf("squa not isKindOfClass MySquare\n");
}
printf("----------------------------------------------\n");
//-(BOOL) respondsToSelector: (SEL) selector 用於判斷對象或者類型是否有能力
迴應指定的方法。
if([squa respondsToSelector: @selector(initWithSize:)]){
printf("squa respondsToSelector initWithSize:\n");
}else{
printf("squa not respondsToSelector initWithSize:\n");
}
if([MySquare respondsToSelector: @selector(alloc)]){
printf("MySquare respondsToSelector alloc\n");
}else{
printf("MySquare not respondsToSelector alloc\n");
}
if([rec respondsToSelector: @selector(initWithWidth:andHeight:)]){
printf("rec respondsToSelector initWithWidth:andHeight:\n");
}else{
printf("rec not respondsToSelector initWithWidth:andHeight:\n");
}
printf("----------------------------------------------\n");
//+(BOOL) instancesRespondToSelector: (SEL) selector 用於判斷類產生的實例
是否是由有能力迴應指定的方法。
if([MySquare instancesRespondToSelector: @selector(initWithSize:)]){
printf("MySquare instancesRespondToSelector initWithSize:\n");
}else{
printf("MySquare not instancesRespondToSelector initWithSize:\n");
}
if([MySquare instancesRespondToSelector: @selector(alloc)]){
printf("MySquare instancesRespondToSelector alloc\n");
}else{
printf("MySquare not instancesRespondToSelector alloc\n");
}
printf("----------------------------------------------\n");
//-(id) performSelector: (SEL) selector 用於動態調用類或者對象上的一個方法。
id x_id=[rec performSelector: @selector(area)];
[MyRectangle performSelector: @selector(alloc)];
[rec release];
[squa release];
}
12. 類別Category: 如果你想擴充一個類的功能,但又不想使用繼承,你可以選擇類別。
下面我們寫一個Fraction 的類別,爲Fraction 類增加計算兩個分數的加減乘除的方法。
FractionMath.h
#import "Fraction.h"
@interface Fraction (Math1)
-(Fraction*) mul: (Fraction*) f;//乘法,就是傳入一個Fraction 作爲參數,與當前的Fraction
進行計算
-(Fraction*) div: (Fraction*) f;//除法
@end
@interface Fraction (Math2)
-(Fraction*) add: (Fraction*) f;//加法
@end
其實類別就是在你想要擴展的@interface 的名字後面加個( ),裏面寫上類別的名字,這個名
字必須是唯一的,也就是說一個@interface 只能有一個類別爲XXX 的Category。
FractionMath.m
#import "FractionMath.h"
@implementation Fraction (Math1)
-(Fraction*) mul: (Fraction*) f{
return [[Fraction alloc] initWithNumerator: numerator*[f numerator]
denominator: denominator*[f denominator]];
}
-(Fraction*) div: (Fraction*) f{
return [[Fraction alloc] initWithNumerator: numerator*[f denominator]
denominator: denominator*[f numerator]];
}
@end
@implementation Fraction (Math2)
-(Fraction*) add: (Fraction*) f{
return [[Fraction alloc] initWithNumerator:
numerator*[f denominator] + denominator*[f numerator]
denominator: denominator*[f denominator]];
}
@end
類別的實現類也就是類型名後後面加個( ),裏面寫上類別的名字,其他的沒有什麼不同的。
上面唯一可能會讓你頭暈的就是加減乘除的實現代碼,實際上就是嵌套調用,寫成了一個語
句而已。拿add 的實現來說,就是創建要返回的計算結果Fraction 的實例,然後依據分數相
加要先通分的規則,結果的分子initWithNumber 就是第一個分數的分子*第二個分數的分母,
再加上第二個分數的分子*第一個分數的分母,分母denominator 參數就是兩個分數的分母
相乘。
main.m
#import "FractionMath.h"
int main( int argc, const char *argv[] ) {
Fraction *frac1 = [[Fraction alloc] initWithNumerator: 1 denominator: 3];
Fraction *frac2 = [[Fraction alloc] initWithNumerator: 2 denominator: 5];
Fraction *frac3 = [frac1 mul: frac2];
[frac1 print];
printf( " * " );
[frac2 print];
printf( " = " );
[frac3 print];
printf( "\n" );
Fraction *frac5 = [frac1 add: frac2];
[frac1 print];
printf( " + " );
[frac2 print];
printf( " = " );
[frac5 print];
printf( "\n" );
[frac1 release];
[frac2 release];
[frac3 release];
[frac5 release];
return 0;
}
運行GCC 之後,Shell 窗口輸出如下內容:
我們看到沒有使用繼承語法,就爲Fraction 添加了數學運算的方法。另外,利用一個類可以有多個類別的特性(上面的Math1、Math2)。
如果一個@interface 有上百個方法,那麼你可以讓編寫的@implementation 什麼都不實現,然後按照@interface 中的各個方法的功能的不同,在不同的類別中實現不同的方法,這樣可以防止一個@implementation 實現全部的接口而顯得臃腫。
那麼類別與繼承相比,有什麼缺點嗎?
類別不可以聲明新的成員變量,而且一旦你定義的方
法與原始類中的方法名稱相同,那麼原始方法將被隱藏起來,因爲不是繼承結構,你不能在
類別中的方法使用super 激活原始類的同名方法。
類別還有一個功能,就是隱藏方法,我們在Fraction.m 的最後增加如下的內容:
@interface Fraction (Math3)
-(Fraction*) sub: (Fraction*) f;//減法
@end
@implementation Fraction (Math3)
-(Fraction*) sub: (Fraction*) f{
return [[Fraction alloc] initWithNumerator:
numerator*[f denominator] - denominator*[f numerator]
denominator: denominator*[f denominator]];
}
@end
在.m 文件中定義@interface?你沒有看錯,因爲@interface 一旦定義在.m 文件中,它就不能以Header 文件的形式被導入到其他的類了,也就是這樣的@interface 中定義的方法相當於被你給隱藏了,只能這個.m 編譯單元內看見。(只能在當前.m 文件中使用)
此時如果你在main 函數中調用sub 做減法運算,會在編譯器得到一個警告,但是實際上你
還是能夠訪問到sub 方法,因爲Objective-C 是動態語言,只要運行期有這個方法,就能夠
調用。我們把@interface 定義在.m 文件中只能做到讓別人不知道有這個方法,但是如果他有你的.m 文件的源碼或者恰好猜到(這種撞大運的機率太低了)你有sub 方法,還是可以調用的。
類別的應用比較廣泛,譬如:第三方的Objective-C 庫RegexKitLite 就是對NSString、
NSMutableString 做的類別,爲Objective-C 的字符類型增加了正則表達式的功能。
13. 協議@protocol:
前面說過@interface 相當於是Objective-C 的類的原型,與JAVA 中的接口意義是不同的,
Objective-C 中的 @protocol 纔是和JAVA 中的接口等價的東西。例如:Objective-C 的繼承也是單繼承,只允許有一個父類,但是@protocol 是允許多繼承的(按照Objective-C 的說法叫做某類遵從了協議A、協議B,而不是繼承),這些都與JAVA 的接口一致。
Printing.h
@protocol Printing1
-(void) print1;
@end
@protocol Printing2
-(void) print2;
@end
@protocol Printing3 <Printing2>
-(void) print3;
@end
Printing3 <Printing2>的意思是Printing3 遵從Printing2,<>是遵從@protocol 協議的語法。
Fraction.h
#import <Foundation/Foundation.h>
#import "Printing.h"
@interface Fraction: NSObject <Printing1,Printing3>{
int numerator;
int denominator;
}
-(Fraction*) initWithNumerator: (int) n denominator: (int) d;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
@end
Fraction.m
#import "Fraction.h"
@implementation Fraction
-(Fraction*) initWithNumerator: (int) n denominator: (int) d{
self=[super init];
if(self){
[self setNumerator: n];
[self setDenominator: d];
}
}
-(void) setNumerator: (int) n{
numerator=n;
}
-(void) setDenominator: (int) d{
denominator=d;
}
-(int) numerator{
return numerator;
}
-(int) denominator{
return denominator;
}
-(void) print1{
printf("1:%d/%d\n",numerator,denominator);
}
-(void) print2{
printf("2:%d/%d\n",numerator,denominator);
}
-(void) print3{
printf("3:%d/%d\n",numerator,denominator);
}
@end
main.m
#import "Fraction.h"
int main (int argc , const char *argv[])
{
Fraction *frac=[[Fraction alloc] initWithNumerator: 3 denominator: 5];
<Printing1> p1=frac;//使用protocol 類型,相當於JAVA 中使用接口類型作爲對象
的引用List list=ArrayList 的實例。
//也可以寫作 id <Printing1> p1=frac;
[p1 print1];
id<Printing1,Printing2,Printing3> p2=frac;
//從這裏可以看出id 是一個泛型對象,在id 後面使用<>作爲泛型參數可以明確的
告訴別人你想把id 當作哪些種協議去使用,當然你可以不寫泛型參數。
[p2 print2];
[p2 print3];
//-(BOOL) conformsToProtocol: (Protocol*) prot 用於判斷對象是否遵從某
個protocol。
if([frac conformsToProtocol: @protocol(Printing1)]
&&[frac conformsToProtocol: @protocol(Printing2)]
&&[frac conformsToProtocol: @protocol(Printing3)])
{
printf("YES");
}else{
printf("NO");
}
[frac release];
return 0;
}