第04章 對象的類型和動態綁定
Objective-C的一個重要特徵就是動態性,本章將對Objective-C的動態類型(dynamic typing)和動態綁定(dynamic binding)進行說明。
4.1 動態綁定
4.1.1 什麼是動態綁定
Objective-C中的訊息是在運行時才去綁定的。運行時系統首先會確定接受者的類型(動態類型識別),然後根據訊息名在類的方法列表裡選擇相應的方法執行,如果沒有找到就到父類中去繼續尋找,假如一直找到NSObject也沒有找到就要調用的方法,就會報告不能識別訊息的錯誤。
動態綁定(dynamic binding)指的就是在程式執行時才能確定對象的屬性和需要相應的訊息。
4.1.2 多態
在物件導向的程式設計理論中,多態(polymorphism)是指,同一操作作用不不同的類的執行個體時,將產生不同的執行結果。即不同的類的對象收到相同的訊息時,也能得到不同的結果。
polymorphic [ˌpɒlɪ'mɔ:fɪk] adj. 多形的,多態的,多形態的; 多晶形
polymorphism [ˌpɒlɪ'mɔ:fɪzəm] n. 多型現象,多態性; 多機組合形式; 多型
prism |ˈprɪzəm| noun 稜鏡
多態是物件導向編程的一個重要特徵,大大增強了軟體的靈活性和擴充性。
例如若使用面向過程的思想,則每增加一種處理就有可能需要增加一個分支,而且可能需要在所有相關的地方增加處理。但是使用多態,就可以大大減少類似情況,可能只需要增加一個類即可。
4.2 作為類型的類
4.2.1 把類作為一種類型
我們可以把定義好的類 作為 對象的類型。如NSObject *object;
4.2.2 null 指標nil
如果給nil變數發送訊息會怎麼樣呢。雖然這種寫法是完全有效,但是運行時不會有任何作用,訊息也不會被發送。
如下例子:
if((val = [list entryForKey:”NeXT]) != nil)
{
[val increment]; //increment |ˈɪŋkrəmənt| noun 增加、增值
}
給nil變數發送訊息,訊息也不會被發送,所以上面的程式就等價於下面的程式。
val = [list entryForKey:”NeXT”];
[val increment];
第二種寫法雖然使程式變得更簡潔,但卻使程式變的更難理解。因為程式的本意是傳回值為nil時不做任何處理。另外,不注意的話這種寫法也會帶來各種各樣的錯誤,例如下面這段程式。
if ((val = [list entryForKey:”NeXT”]) != nil)
{
[val setValue:n++];
}
雖然val是nil是訊息不會被發送,但是並不意味著[val setValue:n++]不會被執行。如果上面這段代碼中省略了val != nil的判斷,n++就仍然會被執行,這點同判斷語句中的“短路”有所不同。
那麼,如果向nil發送訊息,那麼這個訊息的傳回值是什麼。一般來說,如果訊息對應的方法的傳回值是一個對象,那麼將返回nil;如果訊息對應的方法的傳回值是指標類型,將返回NULL;如果訊息對應方法的傳回值是整數類型,則將返回0。而如果傳回值的類型是以上這幾種類型以外的類型,例如結構體或者實數,那麼傳回值將是未定意的;
typedef struct
{
int one;
int two;
} MyTestStruct;
- (MyTestStruct)structTestFunc
{
MyTestStruct result = {1,2};
return result;
}
test = nil;
MyTestStruct myStruct = [test structTestFunc];
NSLog(@"%d,%d,%lu",myStruct.one,myStruct.two,sizeof(myStruct));//0,0,8
NSLog(@"%d,%d,%lu",[test structTestFunc].one,[test structTestFunc].two,sizeof([test structTestFunc]));//0,0,8
4.2.3 靜態類型檢查
雖然在Objective-C中id資料類型可以用來儲存任何類型的對象,但絕大多數情況下我們還是將一個變數聲明為特定類的對象,這種情況稱為靜態類型。使用靜態類型時,編譯器在編譯時間可以進行類型檢查,如果類型不符合會提示警告。
在Objective-C中,id類型是一種通用的資料類型,類似於C語言的(void*),可以用來儲存任何類的對象。在程式執行期間,這種資料類型真正的優勢就會體現出來,使用id類型結合多態,可以使程式具備更大的靈活性和延展性。但程式變靈活的同時,也需要付出代價,編譯器不會對id類型的變數進行檢查,這會導致程式更容易出錯。
在一些複雜的程式中,使用強制類型轉換時,也一定要注意類型,否則就會引發執行階段錯誤。
對於NSObject對象,即使進行強制類型轉換,實際執行時的方法也是有對象本應有的類型決定。
4.2.4 靜態類型檢查的總結
(6)若要判斷到底是哪個類的方法被執行了,不要看變數所聲明的類型,而要看實際執行時這個變數的類型。
(7)id類型並不是(NSObject*)類型,id類型和其他類之間沒有繼承關係。
C++和Java的多態是基於類階層的,可以通過子類重寫父類中的方法來實現多態。這種類型的語言無法實現那種沒有繼承關係的多態,而OC中結合id類型可以做到這一點。
4.3 編程中的類型定義
4.3.1 簽名不一致時的情況
訊息選取器(message selector)中並不包含參數和傳回值的類型的資訊,訊息選取器和這些類型資訊結合起來構成簽名(signature),簽名被用於在運行時標記一個方法。介面檔案中方法的定義也叫做簽名。
NSArray * arry = @[@1,@2];
[self signature:(NSString *)arry number:2];
- (void)signature:(NSString*)str number:(NSInteger)num
{
NSLog(@"%@,%p,%u",str,str,num);
/*
2016-06-01 17:12:53.207 Test[1153:80130] (
1,
2
),0x7d276fa0,2
*/
}
Cocoa提供了類NSMethodSignature,以物件導向的方式來記錄方法的參數個數、參數類型和傳回值類型等資訊。這個類的執行個體也叫做方法簽名。
重載(over loading)指的就是一個函數、運算子或方法定義有多種功能,並根據情況來選擇合適的功能。
OC是動態語言,參數的類型是在運行時確定的,所以不支援這種根據參數類型的不同來調用不同函數的重載。OC可以通過動態綁定讓同一個訊息選取器執行不同的功能來實現重載。
4.3.2 類的前置聲明
通過編譯指令@class告知編譯器某個符號是一個類名。這種寫法稱為類的前置聲明(forward declaration)
class指令後面可以一次接很多個類,用“,”號分割,用“;”表示結束。前置聲明也可以聲明多次。如:
@class NSString,NSArry,NSMutableArry;
@class Volume;
通過使用@class可以提升程式整體的編譯速度。但是要注意的是,如果新定義的類中要使用原有類的具體成員或方法,就一定要引入原有類的標頭檔。
@class的另外一個好處是,當多個介面出現類的嵌套定義時,如果只是相互包含對方的標頭檔無法解決,則只能通過類的前置聲明來解決。
4.3.3 強制類型轉換的使用樣本
雖然強制類型轉換的功能很強大,但會讓編譯器的類型檢查變得沒有意義,所以要盡量少用。不得不使用的情況下,要重新思考設計是否合理。
4.4 執行個體變數的資料封裝
4.4.1 執行個體變數的存取權限
ClassA.h
//
// ClassA.h
// Test
//
// Created by ranzhou on 16/6/1.
// Copyright © 2016年 ranzhouee. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ClassA : NSObject
{
NSString *A_default; //預設為protected
@public
NSString *A_public;
@protected
NSString *A_protected;
@private
NSString *A_private;
}