Objective-C如何自己實現一個for-each語法形式


我們在用Objective-C編寫程序時,很多時候會用到NSArray來作為線性列表來使用。我們在枚舉這個數組所有元素的使用可以通過下列方法進行:

for(id obj in anArray)
{

}

這種方式在編程語言術語中也被稱為for-each形式。在C++11以及Java 5中,上述的in使用冒號:來表示。


那么我們在Objective-C中是否可以自己定義一個類來實現for-each形式呢?當然可以!我們可以通過兩種方式來實現這種簡單的for-each語法形式。

1、通過繼承NSEnumerator類,並且重寫其- (NSArray*)allObjects方法以及- (id)nextObject方法來實現。

2、通過實現NSFastEnumeration協議,並實現其- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id*)stackbuf count:(NSUInteger)len方法。


由於NSEnumerator類比較簡單,我們可以通過看下面的代碼示例即可理解。但是由於Objective-C只有單繼承,因此使用對NSFastEnumeration協議的實現會更加靈活。而且這里比較復雜的是countByEnumeratingWithState方法的實現。下面先對這個方法做個簡單介紹。

首先,當你的類實現了NSFastEnumeration協議並實現了countByEnumeratingWithState之后,在用for-each時,這個countByEnumeratingWithState可能需要被執行多次,依賴於你所要枚舉的元素個數。因此,這個方法的返回值指示了當前所要枚舉的數組的元素個數,如果返回0,則說明枚舉全部完成。

參數stackbuf是指向方法調用者(即消息發送者)所分配的棧空間。這個是由編譯器自動分配的,一般不需要程序員自己干涉。

參數len表示棧空間分配的大小(sizeof(id) * len個字節),也就是說單次枚舉最大能放多少元素。

參數state指向由編譯器給我們分配好的一個NSFastEnumerationState結構體變量地址。這個參數需要我們實現中自己來設置。我們先看這個結構體的定義:

typedef struct {
    unsigned long state;    // 表示當前狀態,初始為0
    id __unsafe_unretained *itemsPtr;    // 指向當前所要枚舉的數組首地址
    unsigned long *mutationsPtr;    // 用於檢測,所要枚舉的對象是否發生了改變
    unsigned long extra[5];    // 這里可以由實現者隨意存放必要的額外數據
} NSFastEnumerationState;

對於這個結構體變量,itemsPtr與mutationsPtr必須進行設置,並且不能為空,除非,此次枚舉直接返回0。


下面我們通過代碼示例來詳細描述這兩種方法的使用:

//
//  main.m
//  objCTest
//
//  Created by Zenny Chen on 12-2-7.
//  Copyright (c) 2014年 Neon Media Studio. All rights reserved.
//

#import <Foundation/Foundation.h>


@interface MyIterator : NSEnumerator
{
@private
    
    NSArray *mArray;
    int mCurrIndex;
}

@end

@implementation MyIterator

- (instancetype)init
{
    self = [super init];
    
    mArray = [@[@1, @2, @3, @4, @5, @6, @7, @8] retain];
    
    return self;
}

- (void)dealloc
{
    if(mArray != nil)
    {
        [mArray release];
        mArray = nil;
    }
    
    [super dealloc];
}

- (NSArray*)allObjects
{
    return mArray;
}

- (id)nextObject
{
    if(mCurrIndex == [mArray count])
        return nil;
    
    return [mArray objectAtIndex:mCurrIndex++];
}

@end


@interface MyFastIterator : NSObject<NSFastEnumeration>
{
@private
    
    NSArray *mArray;
    NSNumber* mNumbers[100];
}

@end

@implementation MyFastIterator

- (instancetype)init
{
    self = [super init];
    
    // 先對mArray進行初始化
    mArray = [@[@100, @200, @300, @400, @-1, @-2, @-3, @-4,
                @1, @2, @3, @4, @5, @6, @7, @8,
                @11, @12, @13, @14, @15, @16, @17, @18] retain];
    
    int i = 0;
    for(NSNumber *num in mArray)
        mNumbers[i++] = num;
    
    return self;
}

- (void)dealloc
{
    if(mArray != nil)
    {
        [mArray release];
        mArray = nil;
    }
    
    [super dealloc];
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id*)stackbuf count:(NSUInteger)len
{
    // 我們用state來表示狀態。狀態為1返回零,說明迭代結束;若狀態為0,則繼續迭代
    // state初始值為零
    if(state->state > 0)
        return 0;
    
    // mutationsPtr不能為空,如果假定在枚舉過程中不發生修改,一般指向self
    state->mutationsPtr = (unsigned long*)self;
    
    // 我們將當前剩余長度放在state的extra[0]之中
    NSUInteger retCount = state->extra[0];
    
    // 所要枚舉的數組指針首地址;extra[1]起始值為零
    state->itemsPtr = &mNumbers[state->extra[1]];
    
    // 若為零,說明這是第一次迭代
    if(retCount == 0)
        retCount = [mArray count];
    
    // 這里需要判斷當前數組長度是否超過了本次枚舉所設置的最大長度
    if(retCount > len)
    {
        // 設置extra[0],存放剩余所要枚舉的元素個數
        state->extra[0] = retCount - len;
        
        // 設置extra[1],存放后一次枚舉起始元素索引
        state->extra[1] += len;
        
        retCount = len;
    }
    else
    {
        // 若剩余所要枚舉的元素個數小於最大限制個數,則狀態變1,使得后一次迭代能直接結束
        state->state++;
    }
    
    // 返回這次所要枚舉的數組一共含有多少元素
    return retCount;
}

@end


int main (int argc, const char * argv[])
{
    @autoreleasepool
    {
        // insert code here...
        
        MyIterator *it = [[MyIterator alloc] init];
        
        NSLog(@"The elements are: ");
        for(NSNumber *num in it)
            printf("%ld  ", [num integerValue]);
        puts("\n");
        
        [it release];
        
        MyFastIterator *iter = [[MyFastIterator alloc] init];
        
        NSLog(@"The elements are: ");
        
        for(NSNumber *num in iter)
        {
            printf("%ld  ", [num integerValue]);
        }
        puts("");
        
        [iter release];
    }
    
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM