2009-07-08 79 views
36

isKindOfClass: method documentation in NSObject:使用isKindOfClass安全嗎:針對NSString實例來確定類型?

使用上的一類集羣表示的對象此方法時要小心。由於類集羣的性質,您返回的對象可能並不總是您所期望的類型。

文檔然後繼續給的,爲什麼你不應該問像一個NSArray實例的以下的例子:

// DO NOT DO THIS! 
if ([myArray isKindOfClass:[NSMutableArray class]]) 
{ 
    // Modify the object 
} 

我們給出了不同的使用的一個例子,假設我有一個我想確定是否有NSString或NSArray的NSObject實例。

這兩種類型都是類集羣 - 但從上面的文檔看來,危險在於對isKindOfClass的回答:太肯定(有時候當你真的沒有可變數組時,回答YES),而提出問題關於羣集中的簡單成員資格仍然有效。

一個例子:

NSObject *originalValue; 

// originalValue gets set to some instance 

if ([originalValue isKindOfClass:[NSString class]]) 
    // Do something with string 

這是假設是正確的?使用isKindOfClass真的很安全:針對類集羣實例來確定成員資格?我特別感興趣的是無處不在的NSString,NSArray和NSDictionary的答案,但是我很想知道它是否可泛化。

+2

您在3年的時間裏對此有所瞭解嗎?什麼樣的愚蠢的`NSMutableArray`子類不會是可變的? – 2011-08-21 20:13:49

+1

從實踐經驗來看,它並不重要;正如你所說我只見過可變的NSMutableArrays,而且我不認爲我曾經使用過一個子類(至少有一個是由第三方製作的)。 Objective-C的大部分內容都是關於不明確的約定,我認爲您可以確定您可能遇到的任何NSMutableArray子類的行爲方式對其他任何Objective-C程序員都有意義。另外,我還要補充一點,我沒有遇到過必須做這樣的檢查的情況 - 任何有數據流的路徑應該總是可變的或不可變的。 – 2011-08-21 20:19:25

+0

同意,當你閱讀API時會遇到一些附帶的問題,但很少在野外。對我來說,這屬於「不要寫`if'這個'else'條件沒有意義的大類'或類似的東西。 – 2011-08-21 22:04:40

回答

30

文檔中的警告使用NSMutableArray示例。假設某些開發人員使用NSMutableArray作爲他自己實現的新類型數組CoolFunctioningArray的基類。這個CoolFunctioningArray由設計是不可變的。但是,isKindOfClass將返回YESisKindOfClass:[NSMutableArray class],這是真的,但是按設計不是。

isKindOfClass:將返回YES如果接收者某處繼承了作爲參數傳遞的類。 isMemberOfClass:僅當接收者是僅作爲參數傳遞的類的實例(即不包括子類)時纔會返回YES

通常isKindOfClass:不安全用於測試成員身份。如果實例爲isKindOfClass:[NSString class]返回YES,那麼只知道它將響應NSString類中定義的所有方法,但您無法確切知道這些方法的實現可能會執行哪些操作。有人可能會將NSString分類爲引發長度方法的例外。

我認爲你可以使用isKindOfClass:進行這種測試,特別是如果你正在處理自己的代碼,你(作爲一個好的撒瑪利亞人)設計的代碼會以我們所有人期望的方式進行響應(例如NSString子類的長度方法返回它表示的字符串長度而不是引發異常)。如果您使用了許多奇怪開發人員的外部庫(例如CoolFunctioningArray的應用程序開發人員),則應謹慎使用isKindOfClass方法,並最好使用isMemberOfClass:方法(可能需要多次來測試一組課程)。

+1

用於說明isKindOfClass和isMemberOfClass之間的區別。我碰巧也需要這個答案。 – 2012-10-04 08:05:06

12

您正在閱讀警告錯誤。它所說的僅僅是因爲它在內部被表示爲可變的東西,並不意味着你應該試圖改變它,因爲改變它可能會違反你和任何你得到數組的人之間的契約;它可能違反了他們不會改變它的假設。

但是,isKindOfClass:給出的測試肯定仍然有效。如果[something isKindOfClass:[NSMutableArray class]],那麼它是NSMutableArray或其子類的實例,這非常意味着它是可變的。

5

這是蘋果文檔中的一個奇怪的警告。這就像是在說:「小心這個功能,因爲如果你將其與其他破碎的代碼一起使用,它將無法工作。」但你可以用任何東西來說。

如果設計遵循它應該的替代原則,那麼KindOfClass將起作用。 (wikipedia: Liskov Substitution Principle

如果有些代碼違反了原則,通過返回一個不響應addObject的NSMutableArray子類的實例:以及其他NSMutableArray方法,那麼該代碼有一個問題......除非它只是一個小進制臨時黑客...

13

讓我們考慮下面的代碼:

if ([dict isKindOfClass:[NSMutableDictionary class]]) 
{ 
    [(NSMutableDictionary*)dict setObject:@1 forKey:@"1"]; 
} 

我認爲蘋果的音符問題引述的真正目的是,這種代碼很容易導致崩潰。這裏的問題在於與CoreFoundation進行免費橋接。讓我們更詳細地考慮4個變種:

NSDictionary* dict = [NSDictionary new]; 
NSMutableDictionary* dict = [NSMutableDictionary new]; 
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 

確實,在所有4個變種中,dict的真實類將是__NSCFDictionary。所有4個變體都將通過第一個代碼片段中的測試。

*終止應用程序由於未捕獲的異常 'NSInternalInconsistencyException',原因是::但2例(NSDictionary中和CFDictionaryRef聲明),我們將與日誌類似的東西崩潰「 - [__ NSCFDictionary的setObject:forKey:]:變異的方法發送到不可變的對象'

所以,事情變得更清晰一點。在2種情況下,我們可以修改對象,2種情況下不允許修改對象。它依賴於創建函數,可能的可變性狀態由__NSCFDictionary對象本身來跟蹤。

但是,爲什麼我們使用相同的類爲可變和不可變的對象? 可能的答案 - 因爲C語言的限制(並且CoreFoundation是一個C API,正如我們所知)。我們在C中遇到什麼問題?可變和不可變字典類型的聲明應該反映如下:CFMutableDictionaryRef類型是CFDictionaryRef的子類型。但是C沒有這個機制。但是我們希望能夠在CFDictionary對象的函數中傳遞CFMutableDictionary對象,並且最好不用編譯器發出惱人的警告。我們必須做什麼?

讓我們來看看下面的CoreFoundation類型聲明:

typedef const struct __CFDictionary * CFDictionaryRef; 
typedef struct __CFDictionary * CFMutableDictionaryRef; 

正如我們所看到的,CFDictionary和CFMutableDictionary由同一類型表示,只有在const修飾不同。所以,這裏開始麻煩。

這同樣也適用於NSArray,但情況稍微複雜一點。當你直接創建NSArray或NSMutableArray時,你會得到一些特殊的類(分別是__NSArrayI和__NSArrayM)。對於這個類的崩潰沒有轉載。但是,當你創建CFArray或CFMutableArray時,事物保持不變。所以要小心!