本文的內容主要來源于 Friday Q&A 2010-03-12: Subclassing Class Clusters by Mike Ash,部分內容有增補和調整。
抽象類 - Abstract Class
要給一個類簇創(chuàng)建的子類,需要先知道類簇是什么。而要理解類簇,需要首先理解抽象類(Abstract Class)的概念。
抽象類是一個沒有實現全部功能的類,它需要被子類化,由子類實現抽象類缺少的功能。抽象類不一定是個徹底的空殼,它依然可以包含很多功能,但只有在子類實現了抽象類沒有實現的功能后,這個類才是完整的。
類簇 - Class Cluster
類簇(Class Cluster)是一個繼承自公共抽象(基)類的層次結構。這個公共類提供了統一的接口和許多輔助功能,而核心功能由私有的子類來實現。公共類提供的實例化方法會返回私有子類的實例,使用戶可以在不需要了解這些子類的情況下使用公共類。
例如,NSArray就是一個抽象類,需要其子類提供count和objectAtIndex:這兩個方法的實現。抽象類NSArray提供了各種構建在這兩者之上的方法,例如indexOfObject: ,objectEnumerator , makeObjectsPerformSelector:等等。
NSArray的核心功能由私有子類(如NSCFArray) 實現。NSArray的實例化方法如+arrayWithObjects:或-initWithContentsOfFile:生成的是這些私有子類的實例。
從外部來看, 一般不容易發(fā)現NSArray是一個類簇。但是在對類對象進行自?。╥ntrospection)時(例如使用isKindOfClass:方法),可能會發(fā)現你得到的不是一個NSArray,而是一個NSCFArray或者別的子類;或者在調試時可能會發(fā)現雖然你創(chuàng)建了一個NSArray,但是調試信息上顯示的是NSCFArray。除此之外,NSArray的行為常常與其他任何類一樣,看不出什么特別的。
而在一種特定的情況下類簇的性質非常重要,那就是你自己將公共類子類化。
子類化 - Subclassing
對類簇進行子類化(這意味著對抽象類進行子類化)與子類化一個普通類完全不同。
子類化一個普通類時,超類已經提供了完整的功能。在這種情況下,子類可以不需要實現任何的方法,就已經是一個完全可用的類,此時它的行為與超類一樣。然后,你可以根據需求為這個子類添加其他方法或覆蓋現有方法。
當繼承一個類簇時,超類不提供完整的功能。它提供了許多輔助功能,但你必須自己提供核心功能。這意味著,一個空的子類是無效的。你至少需要實現幾個必要的方法。在關于類簇的術語中,必須實現的那些方法稱為基本方法(primitive methods)。有兩種簡單的途徑確定哪些方法是基本方法。
第一種途徑是查看類簇的文檔,在其中查看 "Subclassing Notes" 搜索單詞 "primitive",文檔會告訴你需要覆蓋(override)哪些方法。
第二種途徑是打開類簇的頭文件。原始方法會在類的主@interface塊中聲明。類簇提供的其他方法則會在分類(categories)中聲明。
如果一個類簇本身是另一個類簇的子類,那你要多加小心。因為你需要實現這兩個類簇所有的原始方法。例如,NSMutableArray本身有五個原始方法,它的超類NSArray有兩個。如果你要子類化NSMutableArray,則必須為所有七個方法提供實現。
- Techniques
現在知道了要實現什么,但是如何實現?主要有三種方式。
方法一,你可以自己從頭實現每個原始方法。舉個例子,假設要編寫一個專門用于保存兩個元素的數組:
@interface MyPairArray : NSArray {
id _objs[2];
}
- (id)initWithFirst:(id)first second:(id)second;
@end
@implementation MyPairArray
- (id)initWithFirst:(id)first second:(id)second
{
if((self = [self init])) {
_objs[0] = first;
_objs[1] = second;
}
return self;
}
- (NSUInteger)count {
return 2;
}
- (id)objectAtIndex: (NSUInteger)index {
if(index >= 2)
[NSException raise: NSRangeException format: @"Index (%ld) out of bounds", (long)index];
return _objs[index];
}
@end
當然,嚴格來講,如何實現原始方法取決于你希望它們做什么。以上只是個例子,通過使用一個C數組實現了MyPairArray的功能。
方法二,你可以內置一個工作實例(working instance),并把對你的調用傳遞給它來處理:
@interface MySpecialArray : NSArray
@property (copy, nonatomic) NSArray *_realArray;
- (id)initWithArray: (NSArray *)array;
@end
@implementation MySpecialArray
- (id)initWithArray: (NSArray *)array {
if((self = [self init])) {
_realArray = [array copy];
}
return self;
}
- (NSUInteger)count {
return [_realArray count];
}
- (id)objectAtIndex: (NSUInteger)index {
id obj = [_realArray objectAtIndex: index];
// do some processing with obj
return obj;
}
// maybe implement more methods here
@end
這種方法允許你重用原始方法的現有實現,然后添加更多功能。
方法三,給類簇添加一個分類(category),而不對其進行子類化。有時候只是需要添加新方法,而不需要修改現有功能。在Objective-C中,你可以在分類中添加新方法:
@interface NSArray (FirstObjectAdditions)
- (id)my_firstObject;
@end
@implementation NSArray (FirstObjectAdditions)
- (id)my_firstObject {
return [self count] ? [self objectAtIndex: 0] : nil;
}
@end
(這個方法名添加了前綴,以防與官方提供的方法造成沖突。)
結論 - Conclusion
類簇與普通類有所不同,但一旦理解了它們的含義,就很容易進行子類化。你需要實現類簇的基本方法 ,可以自己實現也可以通過內置一個類簇的實例來實現。最后,如果你創(chuàng)建子類的唯一目的是添加新方法,請改用分類來實現。
擴展閱讀
類簇-官方文檔