Objective-C 的語法與Cocoa 框架----01
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
{
@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;
}
相關推薦
Objective-C 的語法與Cocoa 框架----01
Objective-C 的語法與Cocoa 框架 //-------------------------------------------------------------------------------- c++ ,obj-c 程式碼對比:(可以參考一下)
Objective-C Block與函數指針比較
其他 可執行文件 調試 objective 運行 obj 子類 函數指針 延長 相似點 1.函數指針和Block都可以實現回調的操作,聲明上也很相似,實現上都可以看成是一個代碼片段。 2.函數指針類型和Block類型都可以作為變量和函數參數的類型。(typedef定義別名
Objective-c 類與方法學習筆記
@interface Shape : NSObject { ShapeColor fillColor; ShapeRect bounds; } - (void) setFillColor: (ShapeColor) fillColor; - (void) setB
objective-c語法基礎(1)
oc由六大模組組成 1.預處理程式命令 2.介面3.實現 4.方法 5.變數 6.宣告和表達 7.註釋 如下: //用於建立介面 @interface SampleClass:NSObject -(void)sampleMethod;//宣告方法 @end 建立類介面,並在其中宣
Objective-C語法之NSSet和NSMutableSet
NSSet和NSMutableSet是無序的, 但是它保證資料的唯一性。當插入相同的資料時,不會有任何效果。從內部實現來說是hash表,所以可以常數時間內查詢一個數據。 ps: 散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把
Objective-C語法之程式碼塊(block)的使用
程式碼塊本質上是和其他變數類似。不同的是,程式碼塊儲存的資料是一個函式體。使用程式碼塊是,你可以像呼叫其他標準函式一樣,傳入引數數,並得到返回值。 脫字元(^)是塊的語法標記。按照我們熟悉的引數語法規約所定義的返回值以及塊的主體(也就是可以執行的程式碼)。下圖是如何把塊
IOS開發開篇之Objective-C語法基礎
首先宣告:本系列技術部落格只作學習之用,不存在其它目的。 作者從在吸收眾多網友經驗基礎上,結合自己在IOS開發的經驗將對IOS開發技術進行總結,以供後來者學習,更希望藉此使自己對IOS開發的深入理解。 一、Objective-C與C的淵源
第二章、Objective-c 語法,類/屬性/函式(iOS學習筆記,從零開始。)
注*需要具備面向物件程式設計基礎。 一、OC常識 Objective-C是C的超集,也就是說C有的Objective-C都有,Objective-C多了C自身沒有的OO(面向物件)特性。Objective-C預設副檔名為 .m 。標頭檔案副檔名跟普通C一樣 .h 。O
Objective-C Block與函式指標比較、分析
本來不應該將OC block與函式指標進行比較的,這兩者除了宣告形式上類似,都可用來實現回撥(CallBack)之外,其不同的地方會更多。 今天從一個小例子開始對Objective-C裡面函式指標和Block進行剖析。 函式指標是C語言裡面就有的,而Objec
Objective-C語法之第一個iPhone應用程式的那些事兒(十)
#import "HelloWorldViewController.h" @implementation HelloWorldViewController - (void)didReceiveMemoryWarning { // Releases the view if it doesn't ha
Swift開發指南:使用Swift與Cocoa和Objective-C(Swift 4)
與Objective-C API進行互動 互操作性是能夠在任何一個方向上與Swift和Objective-C進行介面,讓您訪問並使用以其他語言的檔案中的一些程式碼。當您開始將Swift整合到應用程式開發工作流程中時,瞭解如何利用互操作性來重新定義、改進和增
Using Swift with Cocoa and Objective-C下載
target cocoa 下載地址 obj swift nbsp 地址 bject uil 《Using Swift with Cocoa and Objective-C Building App 》 下載地址 http://download.csdn.net/
iOS開發核心語言Objective C —— 面向對象思維、setter和getter方法及點語法
才幹 各路 alt .net 行為 變量的作用域 fadein 格式 讀取 本分享是面向有意向從事iOS開發的夥伴們。或者已經從事了iOS的開發人員。假設您對iOS開發有極高的興趣,能夠與我一起探討iOS開發。一起學習,共同進步。假設您是零基礎,建議您先
Java與C++語法的區別
有變 程序 mda 一次 高層 修飾 int 屬性 代碼 1. 註釋可以在Java程序中起到文檔標記的作用 類文檔標記: 1)@version 2)@author 3)@param 4)@return 5)@exception 2. Java的字符占兩個
Objective-C高階程式設計:iOS與OS X多執行緒和記憶體管理
這篇文章主要給大家講解一下GCD的平時不太常用的API,以及文末會貼出GCD定時器的一個小例子。 需要學習的朋友可以通過網盤免費下載pdf版 (先點選普通下載-----再選擇普通使用者就能免費下載了)http://putpan.com/fs/cy1i1beebn7s0h4u9/ 1.G
Objective-C語言基礎與總結
什麼是Objective-C Objective-C是C語言的一個超集,具有面向物件的特性,並具備了強大的執行時動態語言特性。 Objective-C的優缺點 優點: 作為C語言的超級,在C語言的基礎上衍生了很多新的語言特徵,封裝的很完善且使用方便,大大降低了程式設
Effective Objective-C 2.0 總結與筆記(第二章)—— 物件、訊息、執行期
第二章:物件、訊息、執行期 “物件”就是“基本構造單元”,開發者可以通過物件來儲存並傳遞資料。物件之間傳遞資料並執行任務的過程就是“訊息傳遞”。程式執行起來後,為其提供相關支援的程式碼就是“Objective-C執行期環境”,它提供了一些使得物件之間能夠傳遞訊息的重要函式,並且包括建
Effective Objective-C 2.0 總結與筆記(第一章)—— 熟悉Objective-C
第一章:熟悉Objective-C 本章主要是對Objective-C進行一個瞭解,通過介紹Objective-C的語法,來講解Objective-C的基礎知識。 第1條:瞭解Objective-C語言的起源 Objective-C語言採用“訊息結構”而非“函式呼叫”
Objective-C學習筆記-NSSet與NSDictionary
1.NSSet與NSArray的區別就是NSSet裡面的值是不可重複且無序的,在查詢速度上NSSet比NSArray更快,而NSDictionary則可以儲存鍵值對,這個鍵值對也是無序的,鍵通常是一個字串(唯一的),而值可以是任意型別的物件 2.和NSArray一樣,N
iOS Objective-C與Swift開發過程的詳細比較
前段時間,本人同時開發了兩個專案,一個用的OC,一個用的Swift。在使用中對兩種語言進行一次梳理與比較。 基礎檔案 OC Swift OC程式裡,一個類會有兩個檔案,.h和.m。.h可以寫屬性、方