CFBagMattt Croath Liu 🚩🌱

Objective-C被夹在了两个世界中间。

在其中一边的世界里,Objective-C遵循着经过深思熟虑的、发扬自[Smalltalk]的面向对象哲学理念,这种理念给我们带来了消息传递和参数命名法等好点子。另一边的世界里则避免不了有很多C的残留思想带来强大的力量和一坨混乱。

越来越多@符号的使用证明了这个一致性危机。

Foundation和Core Foundation的关系里也可以发现这种一致性问题,特别是那一堆无缝连接的类: NSArray / CFArray, NSDictionary / CFDictionary, NSSet / CFSet。这些类可以通过C函数和Objective-C方法传入传出而不需要手动转换。这是抽象化设计的缺陷,但是同时也是写应用时最实用的优化最难以优化部分的绝佳手段。

但是这种无缝连接是Foundation和Core Foundation之间集合类型转换的一个例外:

FoundationCore Foundation无缝转换
NSArray*CFArray*
NSCountedSetCFBag*
N/ACFBinaryHeap
N/ACFBitVector*
NSDictionary*CFDictionary*
NSIndexSet*N/A
NSMapTableN/A
NSOrderedSetN/A
NSPointerArrayN/A
NSPointerFunctionsN/A
NSSet*CFSet*
* 代表同样适用于相应的mutable类型

看表格的第二行, NSCountedSetCFBag。注意:不像Foundation和Core Foundation中的其他类型,他们之间不能无缝转换。相关文档中除了提供了 NSCountedSet 的一些简略信息,没有其他实在的内容能解释存在这样的类型转换方式。。我猜是因为 NSCountedSet 没有对应的可变(mutable)类型,于是这就打破了类似 NSArray 那些支持无缝转换的固有模式。

Bag,一种抽象数据类型

在计算机科学领域集合数据类型的殿堂中,bag没有数组、集合、联合数组、树、图、优先队列那么占有一席之地。

其实bag本身就很晦涩,你可能从没听过这东西。

Bag,或者叫做multiset,是set的一种变体,不同的是bag里同一数据可以出现不止一次。集合中每一个唯一元素会有一个合计数字与其绑定。类似set一样,bag也是顺序不敏感的。

用bag的场景有…咳咳…很少,但有如果它出现你肯定能感觉到那就是bag。大选中统计票数?模拟家庭作业中的概率分布?实现一个Yahtzee骰子游戏?Bag都是你的新选择!

使用CFMutableBag

CFBag 和它的可变类型同类 CFMutableBag 作为bag类型的具体实现,都是非常灵活的。

虽然它们没有像 NSCountedSet 那样方便地面向对象化,但它可以进行的自定义行为却是多种多样的。你可以用带有许多回调的初始化函数来建立一个 CFBag ,这些回调函数定义在 CFBagCallBacks 结构中,该结构详细描述了一个值被插入、删除、比较的方法:

struct CFBagCallBacks {
   CFIndex version;
   CFBagRetainCallBack retain;
   CFBagReleaseCallBack release;
   CFBagCopyDescriptionCallBack copyDescription;
   CFBagEqualCallBack equal;
   CFBagHashCallBack hash;
};
typedef struct CFBagCallBacks CFBagCallBacks;

例如,如果你正在做一个投票统计应用,你可以制定一个用于 retain 回调函数来保证不同大小写和错误拼写的名字能够归类到正确的候选人;也可以用 equal 回调函数来保证当所有选票都统计完时能够计算出票最多的候选人。

CFMutableBag 也有 CFBagApplyFunction,这个函数可以用来改变集合中的值,比如说理顺选举数量之类的。

总而言之,如果你要准备搞一个选举, CFBag 是你的最佳选择。


严肃地讲, CFBag 在它擅长的领域内确实好用,它时刻提醒着你这是标准框架和标准库中的一块隐藏的宝石,而发觉隐藏的宝藏,就是成为一个NSHipster的核心。

同样的,CFBinaryHeap 呢? NSPointerFunctions 呢? NSMapTable 呢?别急,以后我们都会谈到的。


除非另有声明,本文采用知识共享「署名-非商业性使用 3.0 中国大陆」许可协议授权。