從 C++ 到 Objective-C(8):繼承(續)

語言: CN / TW / HK

Protocol 物件

執行時,協議就像是類物件,其型別是 Protocol * 。例如, conformsToProtocol: 方法就需要接受一個 Protocol * 型別的引數。 @protocol 關鍵字不僅用於宣告協議,還可以用於根據協議名返回 Protocol * 物件。

Protocol* myProtocol = @protocol(協議名)

遠端物件的訊息傳遞

由於 Objective-C 的動態機制,遠端物件之間的訊息傳遞變得很簡單。所謂遠端物件,是指兩個或多個處於不同程式,甚至不同機器,但是可以通過代理完成同一任務,或者交換資訊的物件。正式協議就是一種可以確保物件提供了這種服務的有效手段。正式協議還提供了很多額外的關鍵字,可以更好的說明各種引數。這些關鍵字分別是 in , out , inout , bycopy , byrefoneway 。這些關鍵字僅對遠端物件有效,並且僅可以在協議中使用。出了協議,它們就不被認為是關鍵字。這些關鍵字被插入到在協議中宣告的方法原型之中,提供它們所修飾的引數的額外資訊。它們可以告知,哪些是輸入引數,哪些是輸出引數,哪些使用複製傳值,哪些使用引用傳值,方法是否是同步的等等。以下是詳細說明:

  • in :引數是輸入引數;
  • out :引數是輸出引數;
  • inout :引數即是輸入引數,又是輸出引數;
  • bycopy :複製傳值;
  • byref :引用傳值;
  • oneway :方法是非同步的,也就是函式呼叫會立即返回(否則的話,呼叫者會一直堵塞,直到被呼叫函式執行完畢,即使被呼叫者返回值是 void ,也同樣會被阻塞)。它的返回值必須是 void (其它返回值是沒有意義的,因為被呼叫函式是立即返回,必然無法得到正確的返回值)。

例如,下面就是一個返回物件的非同步方法:

-(oneway void) giveMeAnObjectWhenAvailable:(bycopy out id *)anObject;

預設情況下,引數都被認為是 inout 的。如果引數由 const 修飾,則被當做 in 引數。為引數選定是 in 還是 out ,可以作為一種優化手段。引數預設都是傳引用的,方法都是同步的(也就是不加 oneway )。對於傳值的引數,也就是非指標型別的, outinout 都是沒有意義的,只有 in 是正確的選擇。

分類

建立類的分類 categories,可以將一個很大的類分割成若干小部分。每個分類都是類的一部分,一個類可以使用任意多個分類,但都不可以新增例項資料。分類的好處是:

FooPrivateAPI

最後一點尤其重要。很多開發人員都希望標準類能夠提供一些對他們而言很有用的方法。這並不是一個很困難的問題,使用繼承即可實現。但是,在單繼承的環境下,這會造成出現很多的子類。僅僅為了一個方法就去繼承顯得有些得不償失。分類就可以很好的解決這個問題:

C++

class MyString : public string
{
public:
    // 統計母音的數目
    int vowelCount(void);
};

int MyString::vowelCount(void)
{
...
}

Objective-C

@interface NSString ()
// 注意並沒有使用 {}
-(int) myPrivateMethod;
@end

@implementation NSString ()
-(int) myPrivateMethod
{
...
}
@end

在 C++ 中,這是一個全新的類,可以自由使用。

在 Objective-C 中, NSString 是 Cocoa 框架的一個標準類。它是使用分類機制進行的擴充套件,只能在當前程式中使用。注意此時並沒有新增加類。每一個 NSString 物件都可以從這個擴充套件獲得統計母音數目的能力,甚至常量字串也可以。同時注意,分類不能增加例項資料,因此沒有花括號塊。

分類也可以使匿名的,更適合於 private 的實現:

@interface NSString ()
// 注意並沒有使用 {}
-(int) myPrivateMethod;
@end

@implementation NSString ()
-(int) myPrivateMethod
{
...
}
@end

混合使用協議、分類和子類

混合使用協議、分類和子類的唯一限制在於,你不能同時宣告子類和分類。不過,你可以使用兩步來繞過這一限制:

@interface Foo1 : SuperClass  //ok
@end

@interface Foo2 (Category)   //ok
@end

// 下面程式碼會有編譯錯誤
@interface Foo3 (Category)  : SuperClass
@end

// 一種解決方案
@interface Foo3 : SuperClass  // 第一步
@end

@interface Foo3 (Category) // 第二步
@end