OC的多型(執行時)轉

weixin_33716557發表於2016-03-10

在介紹多型之前,我們來回顧下物件導向的三個特性,封裝、繼承、多型

封裝:就是對類中的一些方法、欄位進行保護,不被外界訪問,有一種許可權控制功能,例如OC中有@public、@protected、@private、@package,預設使用@private

繼承:為了程式碼的重用,子類可以繼承父類的方法和變數

多型:多型是指同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。它是物件導向程式設計(OOP)的一個重要特徵,動態型別能使程式直到執行時才確定物件的所屬類,其具體引用的物件在執行時才能確定。動態繫結能使程式直到執行時才確定呼叫物件的實際方法。

C++中的多型性具體體現在執行和編譯兩個方面,編譯時多型是靜態多型(過載、模版),在編譯時就可以確定物件使用的形式,執行時多型是動態多型(虛擬函式抽象類,覆蓋)。

C++使用虛擬函式(虛擬函式表)來實現動態繫結,當基類物件的指標(或引用)指向派生類的物件時候,實際呼叫的是派生類相應的函式。

Objective-c 是動態語言,所以它具有動態型別和動態繫結的特性。Objective-c系統總是跟蹤物件所屬的類。對於型別的判斷和方法的確定都是在執行時進行。那Objective-c是怎麼樣實現多型特性的呢?

二 Objective-c多型

首先看下面程式碼

draw.h檔案

@interfaceDraw :NSObject

@property(nonatomic,strong)NSString*name;

- (void) Print;

- (void) draw;

@end

draw.m檔案

#import"Draw.h"

@implementationDraw

@synthesizename;

- (id) init

{

if(self= [superinit])

{

self.name=@"Draw Demo";

}

returnself;

}

- (void) draw

{

NSLog(@"Draw::draw.......");

}

- (void) Print

{

NSLog(@"i am  %@.",self.name);

}

@end

cricle.h檔案

#import"Draw.h"

@interfaceCircle :Draw

@end

circle.m檔案

#import"Circle.h"

@implementationCircle

- (void) draw

{

NSLog(@"%@:draw circle",self.name);

}

@end

Retangle.h檔案

#import"Draw.h"

@interfaceRetangle :Draw

@end

Retangle.m檔案

#import"Retangle.h"

@implementationRetangle

- (void) draw

{

[superdraw];//通過super關鍵字可以呼叫基類的draw函式

NSLog(@"%@:draw retangle",self.name);

}

@end

我們定義了一個Draw基類,裡面有一個資料成員name,和兩個函式成員draw和Print,Circle和Retangle是從Draw派生的兩個類,他們重寫了基類Draw的draw方法。

程式碼使用

Draw* base = [[Circlealloc]init];

[basedraw];//draw circle

NSLog(@"address:%@",base);

base = [[Retanglealloc]init];

[basedraw];//draw retangle

NSLog(@"address:%@",base);

[basePrint];

輸出結果

2014-04-09 15:34:41.648 duotaidemo[7718:303] Draw Demo:draw circle

2014-04-09 15:34:41.673 duotaidemo[7718:303] address:

2014-04-09 15:34:41.674 duotaidemo[7718:303] Draw::draw.......

2014-04-09 15:34:41.674 duotaidemo[7718:303] Draw Demo:draw retangle

2014-04-09 15:34:41.675 duotaidemo[7718:303] address:

2014-04-09 15:34:41.676 duotaidemo[7718:303] i am  Draw Demo.

使用基類的指標分別指向建立的兩個派生類物件,然後分別呼叫各自的draw函式,通過輸出結果可以發現他們呼叫的是各自的draw方法。由於Retangele沒有重寫基類的Print函式,所有使用[base Print]呼叫的是基類的方法。同時通過address的輸出發現base指向了兩個不同的物件。

小結:

1.與C++ 的多型相比,在Objective-c中是沒有virtual關鍵字的,預設情況下只要子類重寫了父類的方法就實現了覆蓋(這一點和java類似),在Objective-c中同一類中的函式是不能被過載的。

2.在Objective-c中,通過super關鍵字可以呼叫基類的函式,這個在C++中是沒有的,在C++中可通過作用域運算子訪問基類成員。

除了上面的呼叫方式外,我們也可以這樣:

idbase = [[Circlealloc]init];

[basedraw];//draw circle

NSLog(@"address:%@",base);

base = [[Retanglealloc]init];

[basedraw];//draw retangle

NSLog(@"address:%@",base);

[basePrint];

其輸出結果和上面是一樣的

既然Objective-c中沒有像C++一樣的虛擬函式表,那它的多型是怎麼實現的?它的型別系統是怎麼樣構建起來的呢?繼續往下看吧!

三  類物件

雖然Objective-c沒有虛擬函式表,但是它有一個根類NSObject,下面讓我們探究一下這個根類是個什麼東東。

objc.h檔案中關於NSObject的定義

@interfaceNSObject

{

Class isaOBJC_ISA_AVAILABILITY;

}

typedefstructobjc_class*Class;

typedefstructobjc_object {

Class isa;

} *id;

typedefstructobjc_selector*SEL;

typedefid(*IMP)(id,SEL, ...);

詳見:http://opensource.apple.com/source/objc4/objc4-493.9/runtime/objc.h

通過上面的定義我們可以知道以下事實:

1.Class isa 是NSObject類的第一個資料成員。

2.Class 是一個指標,它指向一個objc_class的結構體。

3.id 型別是一個指標,它指向一個objc_object的結構體,該結構體只有一個成員即Class isa;

4.id 型別是一個指標,它指向一個存有objc_class的結構物件的指標的指標。

3.1 isa介紹

以下是蘋果官方文件對isa的介紹說明:

Every object is connected to the run-time system through its isa instance variable, inherited from the NSObject class. isa identifies the object's class; it points to a structure that's compiled from the class definition. Through isa, an object can find whatever information it needs at run timesuch as its place in the inheritance hierarchy, the size and structure of its instance variables, and the location of the method implementations it can perform in response to messages.

例項變數是通過isa成員連結到執行時系統環境中的,任意NSObject的子類都會繼承NSObject的isa成員,而且當NSObject的子類例項化物件時,isa例項變數永遠是物件的第一個例項變數。isa指向該物件的類物件,它是例項和類物件連線的橋樑。

例項變數和類物件的關聯,如下圖所示:

1635840-07c12c76471bff27

下面是類物件(objc_class)的結構體

structobjc_class {

Class isa;                                            /* metaclass */                Class super_class                                  /* 父類的地址 */constchar*name                                  /*  類名稱  */longversion                                        /*  版本    */longinfo                                            /*  類資訊  */longinstance_size                                /*  例項大小  */structobjc_ivar_list *ivars                    /*  例項引數列表*/structobjc_method_list **methodLists/*  方法列表  */structobjc_cache *cache                      /*  方法快取  */structobjc_protocol_list *protocols/*  protocol連結串列*/

} ;

在Objective-C中類也是一種物件,而且在程式執行時一直存在。類物件是一個根據類定義生成的一個結構體,裡面儲存了類的基本資訊, 如:類的大小,類的名稱,類的版本以及訊息與函式的對映表等資訊。類物件所儲存的資訊在程式編譯時確定,在程式啟動 時載入到記憶體中。

3.2 id介紹

由上面的定義我們知道,id型別是一個指向類物件的指標的指標。在Objective-c中,id型別是一種通用的指標型別,id型別可以用來指向屬於任何類的物件(只要該物件是屬於NSObject即成體系)。

id型別的使用如下圖所示:

1635840-6363973b30728732

在使用id型別的時候要注意:

1. id型別本事是一個指標型別,在使用時就不用加*號了,例如上面的例子idbase = [[Circlealloc]init];

2.id型別是通用指標型別,弱型別,編譯時不進行型別檢查

Objective-C可以將物件分為id型別和靜態型別,如果不涉及到多型,儘量使用靜態型別。

在上的例子中我們使用了兩種方式來呼叫派生類函式,第一種使用的即使靜態型別,第二種使用的是id動態型別。在寫程式碼時候,儘量使用靜態型別,靜態型別可更好的在編譯階段而不是執行階段指出錯誤,同時能夠提高程式的可讀性。

四 小結

例項變數中isa成員用於保持其類物件在記憶體的地址,類物件對於所有例項來說在記憶體中只有一份副本,任何一個例項都可以通過 isa成員,訪問類物件所保持的類的資訊,isa成員可以通過類物件獲得當前例項可以訪問的訊息列表,以及訊息對應的函式地址。

Objecive-c使用類物件的形式來實現執行多型,每個物件都儲存其類物件的地址,類物件中儲存了類的基本資訊。類物件是進行動態建立(反射),動態識別,訊息傳遞等機制的基礎。

那麼上面的程式中,函式的呼叫過程時怎麼樣利用類物件的呢?

相關文章