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 makeObjectsPerformSelector:@selector(doSomethingWithString:) withObject:aString];
在这段代码中,每个对象不一定非得是NSString
类型,并且对象也不需要必须实现了doSomethingWithString:
方法(这会引发一个异常:selector not recognized)。
IMP 缓存
我们在这里不会详细解释这个问题,但是的确可以获得 C 函数的内存地址。通过仅查找一次函数地址,可以优化同一个选择器的多次调用。这被称为 IMP 缓存,因为 Objective-C 用于方法实现的数据类型就是 IMP。
调用class_getMethodImplementation()
就可以获得这么一个指针。但是请注意,这是指向实现方法的真实的指针,因此不能有虚调用。它的使用一般在需要很好的时间优化的场合,并且必须非常小心。
算法
STL 中那一大堆通用算法在 Objective-C 中都没有对等的实现。相反,你应该仔细查找下各个容器中有没有你需要的算法。