Objective-C的四种遍历collection的方式

  • 前言
  • C语言中的for循环
    • 遍历数组
    • 遍历字典
    • 遍历set
    • 反向遍历
  • 使用NSEnumerator进行遍历
    • 遍历数组
    • 遍历字典
    • 遍历set
    • 反向遍历
  • 快速遍历
    • 遍历数组
    • 遍历字典
    • 遍历set
    • 反向遍历
  • 基于块的遍历
    • 遍历数组
    • 遍历字典
    • 遍历set
    • 反向遍历
  • 总结

前言

注:本文摘自《编写高质量iOS与OS X代码的52个有效方法》第7章第48条并进行了相关整理和扩充

    我们在编程中,经常性的需要列举collection中的元素,在OC中,有多种形式来实现该功能:

  1. C语言中的for循环
  2. Objective-C 1.0 的 NSEnumerator
  3. Objective-C 2.0 的快速遍历
  4. 基于块的遍历方式

    开发中,我们常常使用第一种和第三种遍历方式,而往往忽略其他几种遍历方式,但实际上,某些情况下使用其他的方法会大幅度简化编码的过程。

注:本文中所讲的collection,包含NSArrayNSDictionaryNSSet这几个频繁使用的类型。

C语言中的for循环

    遍历数组我们最常采用的方法就是采用for循环:

遍历数组

NSArray *anArray = /*...*/;
for (int i = 0 ; i < anArray.count; i ++){
    id object = anArray[i];
    //Do something with 'object'
}

遍历字典

NSDictionary *aDictionary = /*...*/;
NSArray *keys = [aDictionary allKeys];
for(int i = 0; i < keys.count; i ++){
    id key = keys[i];
    id value = aDictionary[key];
    //Do something with 'key' and 'value'
}

遍历set

NSSet *aSet = /*...*/;
NSArray *objects = [aSet allObjects];
for(int i = 0; i < objects.count; i ++){
    id object = objects[i];
    //Do something with 'object'
}

    可以看到,遍历数组的过程还比较简便。但是字典和set相对来说比较复杂。由于字典与set都是无序的,无法根据特定的整数下标来直接访问其中的值,必须先获取字典里的所有的键或是set里的全部对象,为了保存字典里所有的键或是set中的全部对象,我们必须要创建额外的一个数组对象,用于保留collection中的这些元素对象。相对的,释放操作时,也要将这些额外的对象释放掉,执行了本来不需要执行的方法。相比较而言,其他的方法则无需创建这种中介数组

反向遍历

    当然for循环也可以实现反向遍历,只需要将计数器i的值从元素个数递减。就反向遍历而言,相比较其他的方法简便许多。

NSArray *anArray = /*...*/;
for (int i = anArray.count ; i >= 0; i --){
    id object = anArray[i];
    //Do something with 'object'
}

使用NSEnumerator进行遍历

    NSEnumerator是一个抽象的基类,定义中只提供了一个公共方法和一个公共属性供子类来实现。

@property (readonly , copy)NSArray *allObjects;
-(nullable ObjectTye)nextObject;

    其中,nextObject方法返回的是枚举里的下一个对象。每次调用该方法,其内部的数据结构都会更新,使下次调用发放时能返回下一个对象。如果枚举中的全部对象都已返回,再次调用就会返回nil,表示枚举结束。
    Foundation框架中的collection类都实现了这种遍历方式。

遍历数组

NSArray *anArray = /*...*/;
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil){
    //Do something with 'object'
}

    这种方式的优势在于:不论遍历哪种collection,都可以采用如上类似的语法。

遍历字典

NSDictionary *aDictionary = /*...*/;
NSEnumerator *enumerator = [aDictionary keyEnumerator];
id key;
while ((key = [enumerator nextObject]) != nil){
    //Do something with 'key' and 'value'
}

    遍历字典的方式与数组和set有所不同,由于字典中既有键也有值,需要根据给定的键将对应的值取出来。

遍历set

NSSet *aSet = /*...*/;
NSEnumerator *enumerator = [aSet objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil){
    //Do something with 'object'
}

反向遍历

    NSEnumerator具有多种枚举器,如,反向遍历的枚举器,使用它就可以按反方向进行遍历了。

NSArray *anArray = /*...*/;
NSEnumerator *enumerator = [aSet reverseObjectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil){
    //Do something with 'object'
}

    相较for循环而言,上面的代码更具可读性。

快速遍历

    Objective-C 2.0加入了快速遍历的功能,其语法比较简洁,它为for循环添加了in关键字。
    如果某一个类支持快速遍历,只需遵从NSFastEnumeration协议,该协议只定义了一个方法:

-(NSUInteger)countByEnumeratingWithStat:
                          (NSFastEnumerationState*)state 
                                objects:(id*)stackbuffer 
                                  count:(NSUInteger)length;

    该方法允许类实例同时返回多个对象,这就使得循环遍历操作更为高效了。

遍历数组

NSArray *anArray = /*...*/;
for(id object in anArray){
    //Do something with 'object'
}

遍历字典

NSDictionary *aDictionary = /*...*/;
for(id key in aDictionary){
    id value = aDictionary[key];
    //Do something with 'key' and 'value'
}

遍历set

NSSet *aSet = /*...*/;
for(id object in aSet){
    //Do something with 'object'
}

反向遍历

    由于NSEnumerator对象也实现了NSFastEnumeration协议,所以能用来执行反向遍历。

NSArray *anArray = /*...*/;
for(id object in [anArray reverseObjectEnumerator]){
    //Do something with 'object'
}

基于块的遍历

    后来的Objective-C中,又引入了一种新的做法,它可以实现最基本的遍历功能,这就是基于块的遍历方式。除此之外,还有一系列类似的遍历方法,他们可以接受各种选项,以控制遍历操作。
    在遍历数组以及set时,每次迭代都要执行由block参数所传入的,这个块中有三个参数,分别是当前迭代所针对的对象object,当前迭代所针对的下标idx,以及指向布尔值的指针*stop。通过第三个参数所提供的机制,开发者可以用其终止遍历操作。

遍历数组

    NSArray中定义了如下的方法:

-(void)enumerateObjectsUsingBlock:
        (void(^)(id object,NSUInteger idx,BOOL *stop))block;

    遍历方式如下:

NSArray *anArray = /*...*/;
[anArray enumerateObjectsUsingBlock:^(id object,NSUInteger idx,BOOL *stop){
    //Do something with 'object'
    if(needStop){
        *stop = YES;
    }
}];

    虽然,该种方法代码略多一些,但是思路却很清晰,而且在遍历时可以获取对象和下标。开发者可以通过设定stop变量值来终止遍历操作,当然,使用其他几种遍历方式时,也可以通过break来终止循环。

遍历字典

    遍历字典所用的方法略有不同:

-(void)enumerateKeysAndObjectsUsingBlock:
            (void(^)(id key , id object, BOOL *stop))block;

    遍历方式如下:

NSDictionary *aDictionary = /*...*/;
[aDictionary enumerateKeysAndObjectsUsingBlock:^(id key,id object,BOOL *stop){
    //Do something with 'key' and 'value'
    if(needStop){
        *stop = YES;
    }
}];

遍历set

    遍历set的方法与array相同。

NSSet *aSet = /*...*/;
[aSet enumerateObjectsUsingBlock:^(id object,BOOL *stop){
    //Do something with 'object'
    if(needStop){
        *stop = YES;
    }
}];

    这种方式的优点在于:遍历时可以直接从块里获取更多的信息。遍历数组和有序set时可以知道当前所针对的下标,而在遍历字典时,无需额外编码,即可同事获取键与值,省去了根据给定的键来获取对应值这一步。
    另一个好处是能够修改块的方法签名,以此免除类型转换操作,从效果上讲,相当于把本来需要执行的类型转换操作交给了块方法的签名去做。比如,使用“快速遍历”,将字典中的对象转化为字符串,我们可以这样做:

for(NSString * key in aDictionary){
    NSString *object = (NSString *)aDictionary[key];
    //Do something with 'key' and 'object'
}

    如果改用块的方式来遍历,就可以直接在块的方法签名中直接转换:

NSDictionary *aDictionary = /*...*/;
[aDictionary enumerateKeysAndObjectsUsingBlock:
    ^(NSString *key, NSString *obj,BOOL *stop){
    //Do something with 'key' and 'obj'
}];

    之所以能够如此,是因为id类型相当特殊,它可以被其他类型所复写。要是原来的块签名把键值都定义成NSObject*,那就不可以了。该技巧看似不显眼,实际上非常有用,指定对象的精确类型之后,编译器就可以检测出开发者是否调用了该对象所不具备的方法,并在发现这种问题时报错。

反向遍历

    用此方式也可以执行反向遍历。数组、字典、set都实现里前述方法的另一个版本,令开发者可以向其中传入“选项掩码”(option mask):

-(void)enumerateObjectsWithOptions:
                (NSEnumerationOptions)options
                        usingBlock:(void(^)(id obj,NSUInteger idx,BOOL *stop))block;

-(void)enumerateKeysAndObjectsWithOptions:
                (NSEnumerationOptions)options
                               usingBlock:(void(^)(id key,id obj,BOOL *stop))block;

    NSEnumerationOptions的定义如下:

typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
    NSEnumerationConcurrent = (1UL << 0),//并发
    NSEnumerationReverse = (1UL << 1),//反向
};

    NSEnumerationOptions类型是一个enum,其取值可用“按或位”链接。开发者可以请求以并发执行各轮迭代,通过NSEnumerationConcurrent选项即可开启此功能。反向遍历则是通过NSEnumerationReverse选项来实现,当然,只有在遍历数组或有序set等有循序的collection时,这个选项才有意义。
    总体来说,基于块的遍历方式拥有其他的遍历方式都具备的优势,能提供遍历时所针对的下标,在遍历字典时也可以同时提供键与值,而且还有选项可以开启并发迭代功能,因此,代码多一些也是值得的。

总结

  • 本文共介绍了四种遍历方式:
    1. C语言的for循环遍历
    2. NSEnumerator相关方法进行遍历
    3. 快速遍历(for in)
    4. 基于块的遍历
  • for循环遍历的使用率最为普遍, 提供下标,但遍历字典和set时需进行额外的操作
  • NSEnumerator思路清晰,阅读性更佳
  • 快速遍历的语法最为简洁,可以配合NSEnumerator实现反向遍历
  • 基于块的遍历,具有上述方法的所有优点:
    1. 能提供遍历时所针对的下标
    2. 在遍历字典时也可以同时提供键与值
    3. 提供了选项可以开启并发迭代功能
    4. 若提前知道collection中含何种的对象,可以修改块签名,指出对象的具体类型
    5. 但代码量相较而言多一些

你可能感兴趣的:(iOS开发技巧)