美文网首页编写高质量代码的52个有效方法
52个有效方法(48) - 多用块枚举,少用for循环

52个有效方法(48) - 多用块枚举,少用for循环

作者: SkyMing一C | 来源:发表于2018-10-09 10:02 被阅读44次

    在编程中经常需要列举collection中得元素,在当前的Objective-C语言中又多重办法实现此功能。

    for循环
    //Array
    NSArray *anArray = /*...*/;
        for (int i = 0; i < anArray.count; i++)
        {
            id object = anArray[i];
            //Do something with 'object'
        }
    //Dictionary
        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中得值。创建这个附加数组会有额外的开销,而且还会多创建一个数组对象,它会保留collection中得所有元素对象。当然了,释放数组时这些附加对象也要释放,可以要调用本来不需要执行的方法。其它各种便利方式都无需创建这种中介数组。

    • for循环也可以实现反向遍历,计数器的值从“元素个数减1”,每次迭代时递减直到0为止。执行反向遍历时,使用for循环会比其它方式简单许多。

    NSEnumerator

    用Objective-C 1.0中的 NSEnumerator 来遍历NSEnumerator 是个抽象基类,其中只定义了两个方法,供其具体子类来实现:

    -(NSAraay *)allObjects;
    
    -(id)nextObject;
    
    • 其中关键的方法是nextObject,它返回枚举对象里的下个对象。每次调用该方法时,其内部的数据结构都会更新,使得下次调用方法时能返回下一个对象。等到枚举中得全部对象都已返回之后,再调用就将返回nil,这表示达到枚举末端了。

    • Foundation框架中内建的collection类都实现了这种遍历方式。这种写法的功能与标准的for循环相似,但是代码却多了一些。其真正优势在于:不论遍历哪种collection,都可以采用这套相似的语法。

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

    快速遍历

    快速遍历for...in大幅简化了遍历collection所需的语法。

    NSArray *anArray = /* ... */;
        for (id object in anArray)
        {
            // Do something with 'object'
        }
    // Dictionary
        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'
        }
    
    基于block的遍历方式
    //Array
    - (void)enumerateObjectsUsingBlock:(void (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    //Dictionary
    - (void)enumerateKeysAndObjectsUsingBlock:(void (NS_NOESCAPE ^)(KeyType key, ObjectType obj, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    //Set
    - (void)enumerateObjectsUsingBlock:(void (NS_NOESCAPE ^)(ObjectType obj, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    
    • 在遍历数组及set时,每次迭代都要执行由block参数所传入的块。

    • 这个块有三个参数,分别是当前迭代所针对的对象、所针对的下标,以及指向布尔值的指针。

    • 前两个参数的含义不言而喻。而通过第三个参数所提供的机制,开发者可以终止遍历操作。

    NSArray *anArray = /* ... */;
        [anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop)
         {
             // Do something with 'object'
             if (shouldStop)
             {
                 *stop = YES;
             }
         }];
    // Dictionary
        NSDictionary *aDictionary = /* ... */;
        [aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop)
         {
             // Do something with 'key' and 'object'
             if (shouldStop)
             {
                 *stop = YES;
             }
         }];
    // Set
        NSSet *aSet = /* ... */;
        [aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop)
         {
             // Do something with 'object'
             if (shouldStop)
             {
                 *stop = YES;
             }
         }];
    
    • 遍历时可以直接从block里获取更多信息。在遍历数组时,可以知道当前所针对的下标。遍历有序set(NSOrderedSet)时也一样。而在遍历字典时,无须额外编码,即可同事获取键与值,因而省去了根据给定键来获取对应值这一步。用这种方式遍历字典,可以同事得知键与值,这很可能比其他方式快很多,因为在字典内部的数据结构中,键与值本来就是存储在一起的。

    • 能够修改block的方法名,以免进行类型转换的操作,从效果上讲,相当于把本来需要执行的类型转换操作交给block方法签名来做。

    NSDictionary *aDictionary = /* ... */;
        for (NSString *key in aDictionary)
        {
            NSString *object = (NSString*)aDictionary[key];
            // Do something with 'key' and 'object'
        }
    [aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop)
         {
             // Do something with 'key' and 'obj'
         }];
    
    • id类型相当特殊,它可以像本例这样,为其他类型所覆写。要是原来的block签名把键与值都定义成NSObject *,那么些就不行了。此技巧出刊不甚显眼,实则相当有用。指定对象的精确类型之后,编译器就可以检测出开发者是否调用了该对象所不具备的方法,并在发现这种问题时报错。如果能够确知某collection里的对象是什么类型,那就应该使用这种方法指明其类型。
    执行反向遍历
    //Array
    - (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    //Dictionary
    - (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (NS_NOESCAPE ^)(KeyType key, ObjectType obj, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    //Set
    - (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (NS_NOESCAPE ^)(ObjectType obj, BOOL *stop))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    
    • NSEnumerationOptions类型是个enum,其各种取值可用“按位或”(bitwise OR)连接,用以表明遍历方式。反向遍历是通过 NSEnumerationReverse选项来实现的。要注意:只有遍历数组或有序set等有顺序的collection时,这么做才有意义。

    • 总体来看,block枚举法拥有其他遍历方式都具备的优势,而且还能带来更多好处。与快速遍历法相比,它要多用一些代码,可是却能提供遍历时所针对的下标,在,在遍历字典时也能同时提供键与值,而且还有选项可以开启并发迭代功能,所以多写这点代码还是值得的。

    要点
    1. 遍历collection有四种方式。最基本的办法是for循环,其次是NSEnumeratior遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”。

    2. “块枚举法”本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。

    3. 若提前知道待遍历的collection含有何种对象,则应修改块签名,指出对象的具体类型。

    相关文章

      网友评论

        本文标题:52个有效方法(48) - 多用块枚举,少用for循环

        本文链接:https://www.haomeiwen.com/subject/jwzpnftx.html