NSFastEnumeration
快速枚举有两个优点。
一是,实现快速枚举后,你可以直接使用for/in语法遍历你的对象。
二是,如果将快速枚举实现得很好,会大大提高遍历的速度。
实现快速枚举,很简单。只需要实现NSFastEnumeration
协议就可以了,而且这个协议只有一个方法:
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;
NSFastEnumerationState
是什么啊?
typedef struct {
unsigned long state;
id __unsafe_unretained _Nullable * _Nullable itemsPtr;
unsigned long * _Nullable mutationsPtr;
unsigned long extra[5];
} NSFastEnumerationState;
概括地说,这个方法就是用于返回一系列的 C 数组,以供调用者进行遍历。
为什么是一系列的 C 数组呢?因为,在一个for/in
循环中,这个方法其实会被调用多次,每一次调用都会返回一个 C 数组。至于为什么是 C 数组,那当然是为了提高效率了。
每次调用返回一组对象,这样使对象可以成批的被返回给调用者。
为了获得最好的运行效率,这里使用了一个C数组,这样就需要两个参数——数组首地址指针和数组长度。
数组长度被作为返回值返回。
这也是方法名字中count
所代表的意思。
数组的首地址指针由NSFastEnumerationState
结构体中的itemsPtr
字段指定。这两个值共同确定了这个方法返回给调用者的数组。
NSFastEnumeration
被设计成允许返回一个指向内存的指针。
然而,这并不适于所有数据结构,因此它也被设计成允许将内部对象拷贝到调用者提供的一个数组容器中。
在这里,buffer
即是指调用者提供的数组容器,len
是数组容器的大小。
NSFastEnumeration
还被设计成在遍历集合的时候,可以检测到集合的变动。如果检测到集合被修改了,就会抛出一个异常。mutationsPtr
被用于指向会随着集合变动而变化的一个值。
好了,大部分字段和参数的意思都讲解完了。唯一没讲的就是state和extra这两个字段。这两个字段是预留给被调用者的,你可以自由的使用它们来存储任何你觉得有用的数据。
实现
NSArray *collection = @[@"1",@"2",@"3"];
for(id obj in collection)
{
// body
NSLog(@"%@",obj);
}
clang -rewrite-objc xxx.m
查看.cpp
文件
//创建数组
NSArray *collection = ((NSArray *(*)(Class, SEL, const ObjectType *, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, (NSString *)&__NSConstantStringImpl__var_folders_yb_zbqcspv53295085k0yntnvm00000gn_T_Bridge_30637e_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_yb_zbqcspv53295085k0yntnvm00000gn_T_Bridge_30637e_mi_1, (NSString *)&__NSConstantStringImpl__var_folders_yb_zbqcspv53295085k0yntnvm00000gn_T_Bridge_30637e_mi_2).arr, 3U);
{
id obj;
//初始化结构体 NSFastEnumerationState
struct __objcFastEnumerationState enumState = { 0 };
//初始化数组 stackbuf
id __rw_items[16];
id l_collection = (id) collection;
// 第一次调用 - countByEnumeratingWithState:objects:count: 方法,形参和实参的对应关系如下:
// state -> &enumState
// stackbuf -> __rw_items
// len -> 16
_WIN_NSUInteger limit =
((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)
((id)l_collection,
sel_registerName("countByEnumeratingWithState:objects:count:"),
&enumState, (id *)__rw_items, (_WIN_NSUInteger)16);
if (limit) {
// 获取 mutationsPtr 的初始值
unsigned long startMutations = *enumState.mutationsPtr;
do {
unsigned long counter = 0;
do {
if (startMutations != *enumState.mutationsPtr)
objc_enumerationMutation(l_collection);
obj = (id)enumState.itemsPtr[counter++];
{
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yb_zbqcspv53295085k0yntnvm00000gn_T_Bridge_30637e_mi_3,obj);
};
__continue_label_1: ;
} while (counter < limit);
} while ((limit = ((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)
((id)l_collection,
sel_registerName("countByEnumeratingWithState:objects:count:"),
&enumState, (id *)__rw_items, (_WIN_NSUInteger)16)));
obj = ((id)0);
__break_label_1: ;
}
else
obj = ((id)0);
}
如上代码所示,快速枚举其实就是用两层 do/while
循环来实现的,外层循环负责调用 - countByEnumeratingWithState:objects:count:
方法,获取 C 数组,而内层循环则负责遍历获取到的 C 数组。
同时,我想你应该也注意到了它是如何利用mutationsPtr
来检测集合在遍历过程中的突变的,以及使用 objc_enumerationMutation
函数来抛出异常。
正如我们前面提到的,在快速枚举的实现中,确实没有用到结构体 NSFastEnumerationState
中的 state
和extra
字段,它们只是提供给 - countByEnumeratingWithState:objects:count:
方法的实现者自由使用的字段。
推荐阅读
http://nshipster.com/enumerators/