如何对类簇进行子类化

本文的内容主要来源于 Friday Q&A 2010-03-12: Subclassing Class Clusters by Mike Ash,部分内容有增补和调整。

抽象类 - Abstract Class

要给一个类簇创建的子类,需要先知道类簇是什么。而要理解类簇,需要首先理解抽象类(Abstract Class)的概念。

抽象类是一个没有实现全部功能的类,它需要被子类化,由子类实现抽象类缺少的功能。抽象类不一定是个彻底的空壳,它依然可以包含很多功能,但只有在子类实现了抽象类没有实现的功能后,这个类才是完整的。

类簇 - Class Cluster

类簇(Class Cluster)是一个继承自公共抽象(基)类的层次结构。这个公共类提供了统一的接口和许多辅助功能,而核心功能由私有的子类来实现。公共类提供的实例化方法会返回私有子类的实例,使用户可以在不需要了解这些子类的情况下使用公共类。

例如,NSArray就是一个抽象类,需要其子类提供countobjectAtIndex:这两个方法的实现。抽象类NSArray提供了各种构建在这两者之上的方法,例如indexOfObject:objectEnumeratormakeObjectsPerformSelector:等等。

NSArray的核心功能由私有子类(如NSCFArray) 实现。NSArray的实例化方法如+arrayWithObjects:-initWithContentsOfFile:生成的是这些私有子类的实例。

从外部来看, 一般不容易发现NSArray是一个类簇。但是在对类对象进行自省(introspection)时(例如使用isKindOfClass:方法),可能会发现你得到的不是一个NSArray,而是一个NSCFArray或者别的子类;或者在调试时可能会发现虽然你创建了一个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

类簇与普通类有所不同,但一旦理解了它们的含义,就很容易进行子类化。你需要实现类簇的基本方法 ,可以自己实现也可以通过内置一个类簇的实例来实现。最后,如果你创建子类的唯一目的是添加新方法,请改用分类来实现。

扩展阅读
类簇-官方文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,226评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,509评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,523评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,181评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,189评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,642评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,993评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,977评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,527评论 1 326
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,547评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,661评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,991评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,422评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,571评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,241评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,737评论 2 366

推荐阅读更多精彩内容

  • 类簇 在官方文档中的解释 官方文档中的解释 以下是翻译 类簇 类簇是Foundation框架广泛使用的设计模式。类...
    伯陽阅读 618评论 0 2
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,756评论 0 14
  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 1,634评论 1 10
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,120评论 1 32
  • 不同季节的雨有不同的特色。春天的雨绵延,夏天的雨倾盆,秋天的雨凉爽,冬天的雨冰冷。 下面我就来...
    马滢爸爸阅读 227评论 0 2