Objective-C foreach是保证只调用一次的可枚举对象吗?

时间:2014-09-26 19:49:15

标签: objective-c cocoa

让我们假设我正在做

for (UserCollection * userCollection in [smth fetchUserCollections]) {

}

是否可以保证[user fetchUserCollection]在每个开头只调用一次?

或者我不应该指望它?

2 个答案:

答案 0 :(得分:3)

嗯...

我不知道任何官方 Apple或clang文档,保证它只被调用一次。目标C语言没有规范。

另一方面......

你可以指望它只被召唤一次。这就是今天它的工作方式,反复调用它会是一种性能回归,它可能会打破很多今天有用的代码。

例如,您无法做到这一点:

for (Card *card in [deck shuffledArrayOfCards]) {
    ...
}

因为您在每次通话时都会收到新的随机排序。

在幕后,您的程序会在循环开始时调用[smth fetchUserCollections]一次并保存结果。结果是一个实现NSFastEnumeration协议的对象。然后,它会重复向countByEnumeratingWithState:objects:count:对象发送NSFastEnumeration,直到对象停止返回元素。循环向NSFastEnumeration对象发送多条消息,但只向您的smth对象发送一条消息。

答案 1 :(得分:1)

我同意rob mayoff关于NSFastEnumeration。我自己通过下面的快速代码测试验证了它,并且我没有看到为什么for...in结构会因为可枚举集合的任何其他实现而改变的原因。

编辑:好建议!我已经更新了我的测试,有一个静态计数器和一个明显更大的数组,它仍然显示相同的结果 - 枚举对象只收集一次。值得注意的是,在Apple的Fast Enumeration文档中,特别指出“如果在枚举时修改集合,则枚举器会引发异常。”虽然这并不能确切地确认OP所要求的内容,但似乎暗示该集合仅被检索一次。

另外值得注意的是 - 当我运行此测试并将大小设置为NSUIntegerMax时,它引发了一个很好的例外:)

capacity (18446744073709551615) is ridiculous

代码:

#import <Foundation/Foundation.h>

@interface FastEnumer : NSObject
- (NSArray *)enumeratedArrayOfSize:(NSUInteger)size;
@end

@implementation FastEnumer

- (NSArray *)enumeratedArrayOfSize:(NSUInteger)size
{
    // counts how many times this method is called
    static NSUInteger calledCount = 0;
    NSLog(@"(Enumerated) Called: %ld\n", ++calledCount);

    // populates an array of NSStrings of alphabet letters to enumerate
    static NSMutableArray *testArray = nil;
    if (!testArray) {
        testArray = [[NSMutableArray alloc] initWithCapacity:size];
        NSArray *sample = @[@"A", @"B", @"C", @"D", @"E", @"F", @"G", @"H",
                            @"I", @"J", @"K", @"L", @"M", @"N", @"O", @"P",
                            @"Q", @"R", @"S", @"T", @"U", @"V", @"W", @"X",
                            @"Y", @"Z"];
        for (NSUInteger i = 0; i < size; ++i) {
            [testArray addObject:sample[i % sample.count]];
        }
    }
    return testArray;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // flag to note loop behavior
        BOOL started = NO;

        // testing class for the 'for...in' fast enumeration
        FastEnumer *myTest = [[FastEnumer alloc] init];

        // set number of items in array and use Fast Enumeration construct
        NSUInteger size = 1000000000; // 1 billion
        for (NSString *letter in [myTest enumeratedArrayOfSize:size]) {

            // show loop has begun
            if (!started) {
                NSLog(@"Started 'for...in' loop with %@", letter);
                started = YES;
            }
        }
    }
    return 0;
}

输出:

2014-09-26 14:16:45.826 Scratchy[8222:303] (Enumerated) Called: 1
2014-09-26 14:17:40.346 Scratchy[8222:303] Started 'for...in' loop with A
Program ended with exit code: 0