关于Objective-C的for in

栏目: Objective-C · 发布时间: 5年前

内容简介:循环我们知道在命令式编程语言中,循环在结构控制中占有很重要的地位。比如有一个数组,里面放的是整形数。现在有个需求是给这个数组的所有元素 + 1.int array[5] = {1,2,3,4,5};

循环

我们知道在命令式编程语言中,循环在结构控制中占有很重要的地位。比如有一个数组,里面放的是整形数。现在有个需求是给这个数组的所有元素 + 1.

int array[5] = {1,2,3,4,5};

for(int i = 0; i < 5; i++) array[i] = array[i] + 1;

这就实现了这个需求。同样的可以通过 while 循环来实现这个需求

但是在面向对象的语言中,一般处理的元素都是对象。有的时候并不关心index,重点是数组中的元素。所以一般会有这种形式的循环:for in

比如:

NSArray *array = @ [ @ 1 , @ 2 , @ 3 ];

for (NSNumber *number in array) {

}

在{}中,你可以对number进行任意的处理,我们来重点看一下是如何进行for in的

用clang -rewrite-objc -xxx.m得到处理后的代码

NSArray array = ((NSArray *( )(Class, SEL, ObjectType Nonnull const * Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel registerName("arrayWithObjects:count:"), (const id *) NSContainer_literal(3U, ((NSNumber ( )(Class, SEL, int))(void )objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *( )(Class, SEL, int))(void )objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *( )(Class, SEL, int))(void

)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);

{

NSNumber * number;

struct _

objcFastEnumerationState enumState = { 0 };

id __rw_items[16];

id l_collection = (id) array;

NSUInteger limit =
) (id, SEL, struct _
objcFastEnumerationState *, id *,

WIN_NSUInteger))(void *)objc_msgSend)

((id)l_collection,

sel_registerName("countByEnumeratingWithState:objects:count:"),

rw_items, (_WIN_NSUInteger)16);

if (limit) {

unsigned long startMutations = *enumState.mutationsPtr;

do {

unsigned long counter = 0;

do {

if (startMutations != *enumState.mutationsPtr)

enumerationMutation(l_collection);

number = (NSNumber *)enumState.itemsPtr[counter++]; {

};

continue_label_1: ;

} while (counter < limit);

} while ((limit = ((

WIN_NSUInteger (*) (id, SEL, struct _ objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc msgSend)

collection,

sel_registerName("countByEnumeratingWithState:objects:count:"),

rw_items, (

WIN_NSUInteger)16)));

number = ((NSNumber *)0);

1: ;

}

else

number = ((NSNumber *)0);

}

前面的NSArray *array = ... 这一坨代码就是初始化数组,在OC中,像某个对象发消息都会转换成 objc_msgsend(id, sel,...).具体可以参考《Effective Objective-C2.0 》一书

struct __objcFastEnumerationState enumState ={}

typedef struct {

unsigned long state;

id __unsafe_unretained _Nullable * _Nullable itemsPtr;

unsigned long * _Nullable mutationsPtr;

unsigned long extra[5];

} NSFastEnumerationState;

这里是NSFastEnumerationState的定义,通过上面的转换后的代码,可以看出:state,extra 这两个字段系统并没有使用到,我们可以随意使用。

这里初始化了一个enumState,然后 通过调用countByEnumeratingWithState:objects:count:方法得到一个limit,

@ protocol NSFastEnumeration

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;

@ end

这个方法是NSFastEnumeration中定义的一个方法,也就是说要想实现快速枚举,就必须实现这个方法。 该方法返回一个limit,我们可以猜测这个limit就是循环的次数。

我们接着看代码。在判断limit大于0 的时候,把mutationsPtr指针指向的值赋给了startMutations。也就是记录该循环的起始不可变值。在下面有句代码

if (startMutations != *enumState.mutationsPtr)

objc_enumerationMutation(l_collection);

意思是如果 在循环中,该数组的不可变值发生了变化,就会调用objc_enumerationMutation(l_collection),l_collection就是我们将要迭代的数组。

再处理了开始的一些条件后,正式开始了迭代,do..while 循环中通过对itemsPtr进行地址处理,获取对每一次的number。后面的{} 就是我们调用for..in 循环中循环体的代码。

按照我们之前的猜想,limit就是循环的次数,为什么还没有终止处理呢?相反 在对number处理的这层循环外还有一层循环。 可以看到这依然通过 调用countByEnumeratingWithState:objects:count:方法得到一个limit,limit 依然是迭代的终止条件。 why?

可以肯定的是,我们的猜想错了。 该方法返回的limit是循环次数中的一部分,而不是循环的总次数。比如说 array需要处理20次,这个方法可能每次返回的limit是4. 也就是说 countByEnumeratingWithState:objects:count:会被调用5次。 如果重复调用,那我们怎么确定 上一次循环截止的index呢?

我们前面说过 NSFastEnumerationState 结构体中有可以自由发挥的字段,比如 state,我们可以让这个字段记录当前循环的index。

在转换的代码中还有一些诡异的地方,比如: continue_label_1、 break_label_1 , 这个其实是goto标记, 在循环中可以使用continue、break等关键字,就是在触发continue或者break时候,调用goto。

说了这么多,我们看一个具体的实例

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state

objects:(id __unsafe_unretained [])stackbuf

count:(NSUInteger)stackbufLength

{

NSUInteger count = 0;

unsigned long countOfItemsAlreadyEnumerated = state->state;

if(countOfItemsAlreadyEnumerated == 0)

{

state->mutationsPtr = &state->extra[0];

}

if(countOfItemsAlreadyEnumerated < _list.size())

{

state->itemsPtr = stackbuf;

while((countOfItemsAlreadyEnumerated < _list.size()) && (count < stackbufLength))

{

stackbuf[count] = _list[countOfItemsAlreadyEnumerated];

countOfItemsAlreadyEnumerated++;

count++;

}

}

else

{

count = 0;

}

state->state = countOfItemsAlreadyEnumerated;

return count;

}

这份代码是苹果guide文档中的一个sample。上述代码的意思是

  1. 定义一个count,这个是用来记录内层do..while循环的次数
  2. 拿到上次循环时候截止的index
  3. 如果index == 0.也就是第一次处理,这个时候可以跟踪循环中不可变得数据。很明显这里并没有处理。。。
  4. 在没有越界的情况下,拿到stackbuf,这个你可以认为是缓冲数组,用来存储将要遍历的数组。
  5. 4中的stackbuf是有长度限制的,这个count是由系统传出来的,通过转换后的代码,很容易知道是16.但是这个只是在我的机器上处理后的结果(不是唯一的).在这个while中把原数组中的数据拷贝给stackBuf,然后对count,index 加一处理。
  6. 把index传递给state中的state。 返回count
  7. 这个方法可能多次被调用,所以在循环截止的时候,记得把count置为0

在原sample code中,还有一个处理方法,如果你能确定原数组中的元素在内存中是依次递增的,此时你可以直接把原数组的首地址 拷贝给state->itemPtr,这样在外面的循环中,就可以通过对地址增处理,获取正确的数据。 但是前提是 原数组中的元素地址是依次递增的。

其实上面比较有疑惑的地方是第三点,我在网上搜了一下,大部分博客都是说 记录枚举中不可变的变量,然后 在迭代中判断是否发生改变。有的博客直接说传self。 但是这是很不合理的,在苹果的sample code的注释中state->mutationsPtr MUST NOT be NULL and SHOULD NOT be set to self.明确指出 不能为NULL,也不应该是self。

mutationsPtr的类型是unsigned long*,如果是传self,在OC中self肯定不会是unsigned long *类型,如果强转会丢失类型信息。而且在转换的代码中,也看到 会把mutaionsPtr 赋值给一个 unsigned long *类型,而且比较这两个变量的类型也是通过!=符号来判定,由于OC中不存在运算符重载,只是单纯的比较数值的大小。所以我认为这里mutationsPtr 应该是记录数组的长度。在循环的过程中,不应该改变数组的长度。当然了一般情况下,像sample code中一样,传递一个无意义的值就好了。

剩下的就是 结构体state中还存在一个未被系统所使用的unsigned long extra[5],这个可供我们自由使用。

嗯,over!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

解密硅谷

解密硅谷

[美]米歇尔 E. 梅西纳(Michelle E. Messina)、乔纳森 C. 贝尔(Jonathan C. Baer) / 李俊、李雪 / 机械工业出版社 / 2018-12 / 50.00

《解密硅谷》由身处硅谷最中心的连续创业者米歇尔·梅西纳和资深的投资人乔纳森·贝尔联合撰写,二人如庖丁解牛一般为读者深入剖析硅谷成功的原因:从硅谷的创新机制、创业生态、投资领域的潜规则、秘而不宣的价值观等角度,让阅读本书的人能够在最短的时间内,拥有像硅谷人一样的商业头脑,从而快速发现机遇,顺利地躲过创业的坑,熬过创业生死挑战中的劫数,带领初创公司顺利地活下去,并实现快速增长。 如果初创公司能够......一起来看看 《解密硅谷》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具