Swift中的NSFastEnumeration

16

我正在尝试将一个Objective-C项目转换为Swift,但是我无法找到如何使用NSFastEnumeration来处理符合NSFastEnumeration协议的类的对象。

以下是Objective-C中的代码:

//  get the decode results
id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];

ZBarSymbol *symbol = nil;
for(symbol in results)
    // just grab the first barcode
    break;

到目前为止,我尝试找到如何做到这一点,但它似乎不起作用,这里是 Swift 代码:

var results: ZBarSymbolSet = infoDictionary?.objectForKey(ZBarReaderControllerResults) as ZBarSymbolSet

    var symbol : ZBarSymbol? = nil;

    for symbol in results
    {    //just grab first barcode
        break;
    }

出错的条件是 - "ZBarSymbolSet" 没有名为 "Generator" 的成员。

我做错了什么?

这是屏幕截图 enter image description here


1
我也想听到一个真正的解决方案(目前唯一的答案只是说明为什么它不起作用)。NSFastEnumeration是在NSFoundation中广泛使用的协议(NSSetNSHashTableNSMapTableNSPointerArray等),如果要符合SequenceType,则扩展所有这些类似乎是多余的,因为Objective-C已经支持了相同的for-in结构。 - John Estropia
6个回答

23

在浏览了一段时间 Swift 框架文件后,我终于找到了一个很好的类叫做 NSFastGenerator。似乎 NSSet 等也在使用相同的 Generator

对于 ZBarSymbolSet,以下是如何扩展它以支持 for-in 循环:

extension ZBarSymbolSet: SequenceType {
    public func generate() -> NSFastGenerator {
        return NSFastGenerator(self)
    }
}

更新:看起来Swift 2.0的协议扩展已经为我们修复了这个问题!


1
这太棒了,但是苹果公司应该默认为所有符合NSFastEnumeration协议的类执行此操作。目前,我必须为每个我想使用“for...in”的NSFastEnumeration类重复此扩展,这很疯狂。 - matt
尽管Swift 2.0支持协议扩展,但它们不能有继承子句,因此该技术仍然似乎局限于类。 - lukhnos
@lukhnos NSFastEnumeration协议是为旧的Objective-C集合设计的。你不应该将其用于结构体;只需使用SequenceType即可。 - John Estropia

3
以下是 John Estropia 就 Swift 3 的回答:
extension ZBarSymbolSet: Sequence {

    public typealias Iterator = NSFastEnumerationIterator

    public func makeIterator() -> NSFastEnumerationIterator {
        return NSFastEnumerationIterator(self)
    }

}

那么你的for-in循环应该是这样的:
for element in results {
    let symbol = element as! ZBarSymbol
    // ...
}

采用 IteratorProtocol 还可以改进此答案,这样您可以将元素关联类型指定为 ZBarSymbol。我还没有弄清楚如何做到这一点。


3

您定义的类ZBarSymbolSet需要实现Swift SequenceType接口,才能在for <identifier> in <sequence>语法中使用。 SequenceType接口是:

protocol SequenceType : _Sequence_Type {
    typealias Generator : GeneratorType
    func generate() -> Generator
}

因此,你会在错误信息中看到对Generator的提及。

同样,在语法中:

for <identifier> in <sequence> {
  <statements>
}

<identifer> 只在 <statements> 的范围内有效。因此,你在 if 中的第二次使用 symbol 将超出范围并导致错误。一个正确的习惯用法是:

var symbolFound : ZBarSymbol?

for symbol in result {
  symbolFound = symbol
  break
}

if symbolFound ...

当然可以,但是当 ZBarSymbolSet 实现 SequenceType 时,它也会实现 CollectionType 并有 subscript,因此整个'找到第一个元素'的代码就变成了 var symbol = result[0]


由于我正在使用ZBar SDK http://zbar.sourceforge.net这个静态库,所以我无法编辑ZBarSymbolSet。有其他替代方案吗? - Sharon Nathaniel
1
子类化它或扩展它。 - David Berry

2
这里有一种方法,可以不用编写扩展程序来实现。
    var iterator = NSFastEnumerationIterator(collection)
    while let element = iterator.next() {
        // use element
    }

2
Step1: 
extension ZBarSymbolSet: SequenceType {
    public func generate() -> NSFastGenerator {
        return NSFastGenerator(self)
    }
}

Step2:
var results: NSFastEnumeration = info.objectForKey(ZBarReaderControllerResults) as NSFastEnumeration

    var symbolFound : ZBarSymbol?

    for symbol in results as ZBarSymbolSet {
        symbolFound = symbol as? ZBarSymbol
        break
    }
    resultString = NSString(string: symbolFound!.data)

谢谢Alok,但是John的答案解决了我遇到的问题。我认为你的答案会帮助其他人理清整个情况。 - Sharon Nathaniel
在Swift 2中,我在这一行 for symbol in results as ZBarSymbolSet 收到了错误信息 "Could not cast value of type '__NSArrayM' to 'ZBarSymbolSet'"。 - Isuru

1
此外,如果您知道您的ZBarSymbolSet中的所有对象都是ZBarSymbol对象(因为ObjC不强制所有对象都是ZBarSymbol对象),则可以执行以下操作:
extension ZBarSymbolSet {

    public struct ZBarSymbolSetIterator {
        public typealias Element = ZBarSymbol
        private let enumerator: NSFastEnumerationIterator

        init(_ symbols: ZBarSymbolSet) {
            self.enumerator = NSFastEnumerationIterator(symbols)
        }

        public mutating func next() -> ZBarSymbol {
            if let object = self.enumerator.next() {
                return object as? ZBarSymbol
            }
            else { return nil }
        }
    }

    public func makeIterator() -> ZBarSymbolSetIterator {
        return ZBarSymbolSetIterator(self)
    }
}

现在你的for循环将会是这样的:
for element in results {
    // element is a ZBarSymbol
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接