关于Objective-C的for in

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

内容简介:循环我们知道在命令式编程语言中,循环在结构控制中占有很重要的地位。比如有一个数组,里面放的是整形数。现在有个需求是给这个数组的所有元素 + 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!


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

查看所有标签

猜你喜欢:

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

大话设计模式

大话设计模式

程杰 / 清华大学出版社 / 2007-12-1 / 45.00元

本书通篇都是以情景对话的形式,用多个小故事或编程示例来组织讲解GOF(设计模式的经典名著——Design Patterns:Elements of Reusable Object-Oriented Software,中译本名为《设计模式——可复用面向对象软件的基础》的四位作者EIich Gamma、Richard Helm、Ralph Johnson,以及John Vlissides,这四人常被称......一起来看看 《大话设计模式》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

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

HSV CMYK互换工具