需求:对于一个自定义类如何也可以想和NSArray
和NSDictionary
一样可以直接遍历?
本篇目录:
- 解析系统for…in…的实现原理;
- 自己实现一个for…in…的类;
- 简单解释一下
objc_enumerationMutation
是如何抛出异常的。
1. 解析系统for…in…的实现原理
我们来看看苹果在2.0推出来的Fast Enumeration。
引用苹果官方文档的一段总结
The enumeration is more efficient than using NSEnumerator directly
The syntax is concise.
The enumerator raises an exception if you modify the collection while enumerating.
You can perform multiple enumerations concurrently.
翻译过来就是
- 它比之前的
NSEnumerator
更高效 - 语法更简洁
- 如果这个集合在遍历的过程中修改了,会抛出异常
- 可以同时执行对个枚举
Fast Enumeration
是一个协议
1
2
3
4
5
6
7
@protocol NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer
count:(NSUInteger)len;
@end
这个方法的作用是根据具体数据个数返回一定数量的数组供调用者使用的,为什么是一定数量的数组呢,比如说数据源是有5个数据[@”1”,@”2”,@”3”,@”4”,@”5”],若调用者想要2个一组,那么需要3组才能完成;前面提到的一组,其实就是C语言的数组,而这个方法就是用来确定返回一个怎样的数组,方法的返回值就是对应数组的长度。
这个协议方法传3个参数分别是:
state
,它是个结构体;1
2
3
4
5
6typedef struct {
unsigned long state;
id __unsafe_unretained _Nullable * _Nullable itemsPtr;
unsigned long * _Nullable mutationsPtr;
unsigned long extra[5];
} NSFastEnumerationState;state
这个参数在for…in…的方法内部是没有使用的,是留给调用者备用的,用来记录一些状态;itemsPtr
就是C数组的指针,它和方法的返回共同构成了C语言数组;mutationsPtr
这个字段是用来记录在遍历的过程中,被遍历的对象有没有被改变,从而可以抛出异常;extra
这个和state
字段一样,在for…in…的方法内部是没有使用的也是没有使用的,留给调用者使用的。
buffer
它是一个缓冲区,其实是一个C数组,因为在内存中不是所有的对象都是内存连续的,针对那些内存不连续,方法提供一个内存区域,调用者把数组都放到这个缓冲区,他的长度由len决定;len
上面已经提到就是定义buffer
长度的。
为何我会这样解释这些属性呢,我们来看看for…in…的C++实现,就可以一一验证上面的说法了
先写一个demo
1 | #import <Foundation/Foundation.h> |
使用命令
1 | clang -rewrite-objc main.m |
可以得到
1 |
|
对于上面的源码进行了一些必要的注释帮助大家理解,整个方法下来,并没有看到state
和extra
字段,这也验证了之前的说法。
2. 自己实现一个for…in…的类
这里参照苹果的官方demo写了一个简单的例子
对于countByEnumeratingWithState:objects:count:
我们有两种方法来实现
- 对于在内存中连续的结合来说可以直接返回这段内存的首地址;
- 对于不连续的来说,这个时候就要使用
buffer
了,接下来分别给出两种方式
.h
1 | #import <Foundation/Foundation.h> |
.m
1 | #import "MyFastIterator.h" |
countByEnumeratingWithState:objects:count:
实现
1 |
|
外面调用
1 | - (void)testMyFastIterator { |
其实还有一种简单的写法,直接返回要遍历的对象的方法,前提是遍历的对象实现了countByEnumeratingWithState
方法
1 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len { |
3. 简单解释一下objc_enumerationMutation
是如何抛出异常的。
objc_enumerationMutation
方法是如何抛出异常的呢,打开objc4-646的源码中可以看到具体实现
1 |
|
阅读源码可以得出看出:
objc_setEnumerationMutationHandler
方法接收一个函数指针,保存在内部定义的之前声明好的函数static void (*enumerationMutationHandler)(id);
;objc_enumerationMutation
被调用的时候,如果调用者没有实现objc_setEnumerationMutationHandler
的话,此时函数指针enumerationMutationHandler
为nil,就会执行_objc_fatal("mutation detected during 'for(... in ...)' enumeration of object %p.", (void*)object);
,否则就会通过*enumerationMutationHandler
拿到函数并把object
传递出去。
我们来看一个demo
1 | //先初始化一个函数 |
输出
1 | test挂啦 |