我們在用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; }