C++ 标准库是其强大的一个原因。即使它还有一些不足,但是已经能够算作是比较完备的了。这并不是语言的一部分,而是属于一种扩展,其他语言也有类似的部分。在 Objective-C 中,你不得不在 Cocoa 里面寻找容器、遍历器或者其他一些真正可以使用的算法。
容器
Cocoa 的容器比 C++ 更加面向对象,它不使用模板实现,只能存放对象。现在可用的容器有:
- NSArray 和 NSMutableArray:有序集合;
- NSSet 和 NSMutableSet:无序集合;
- NSDictionary 和 NSMutableDictionary:键值对形式的关联集合;
- NSHashTable:使用弱引用的散列表(Objective-C 2.0 新增)。
你可能会发现这其中并没有 NSList 或者 NSQueue。事实上,这些容器都可以由 NSArray 实现。
不同于 C++ 的 vector<T>,Objective-C 的 NSArray 真正隐藏了它的内部实现,仅能够使用访问器获取其内容。因此,NSArray 没有义务为内存单元优化其内容。NSArray 的实现有一些妥协,以便 NSArray 能够像数组或者列表一样使用。既然 Objective-C 的容器只能存放指针,单元维护就会比较有效率了。
NSHashTable 等价于 NSSet,但它使用的是弱引用(我们曾在前面的章节中讲到过)。这对于垃圾收集器很有帮助。
遍历器
经典的枚举
纯面向对象的实现让 Objective-C 比 C++ 更容易实现遍历器。NSEnumerator 就是为了这个设计的:
NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSEnumerator* enumerator = [array objectEnumerator];
NSString* aString = @"foo";
id anObject = [enumerator nextObject];
while (anObject != nil)
{
[anObject doSomethingWithString:aString];
anObject = [enumerator nextObject];
}
容器的 objectEnumerator 方法返回一个遍历器。遍历器可以使用 nextObject 移动自己。这种行为更像 Java 而不是 C++。当遍历器到达容器末尾时,nextObject 返回 nil。下面是最普通的使用遍历器的语法,使用的 C 语言风格的简写:
NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSEnumerator* enumerator = [array objectEnumerator];
NSString* aString = @"foo";
id anObject = nil;
while ((anObject = [enumerator nextObject]))
{
[anObject doSomethingWithString:aString];
} // 双括号能够防止 gcc 发出警告
快速枚举
Objective-C 2.0 提供了一个使用遍历器的新语法,隐式使用 NSEnumerator(其实和一般的 NSEnumerator 没有什么区别)。它的具体形式是:
NSArray* someContainer = ...;
for(id object in someContainer) {
// 每一个对象都是用 id 类型 ... }
for(NSString* object in someContainer)
{ // 每一个对象都是 NSString ...
// 开发人员需要处理不是 NSString* 的情况
}
函数对象
使用选择器
Objective-C 的选择器很强大,因而大大减少了函数对象的使用。事实上,弱类型允许用户无需关心实际类型就可以发送消息。例如,下面的代码同前面使用遍历器的是等价的:
NSArray* array = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSString*aString = @"foo";
[array makeObjectsPerformSelect or:@selector(doSomethingWithString:)withObject:aString];
在这段代码中,每个对象不一定非得是 NSString 类型,并且对象也不需要必须实现了 doSomethingWithString: 方法(这会引发一个异常:selector not recognized)。
IMP 缓存
我们在这里不会详细解释这个问题,但是的确可以获得 C 函数的内存地址。通过仅查找一次函数地址,可以优化同一个选择器的多次调用。这被称为 IMP 缓存,因为 Objective-C 用于方法实现的数据类型就是 IMP。
调用 class_getMethodImplementation() 就可以获得这么一个指针。但是请注意,这是指向实现方法的真实的指针,因此不能有虚调用。它的使用一般在需要很好的时间优化的场合,并且必须非常小心。
算法
STL 中那一大堆通用算法在 Objective-C 中都没有对等的实现。相反,你应该仔细查找下各个容器中有没有你需要的算法。