16

I am trying to convert an Objective-C project to swift, but I am unable to find how to use NSFastEnumeration for an object of a class that conforms to NSFastEnumeration.

Here is the code in ObjC:

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

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

so far I tried to find how to do this, but this doe not seems work, here is the swift code:

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

    var symbol : ZBarSymbol? = nil;

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

the error comes in for condition - "ZBarSymbolSet" does not have a member named "Generator"

What am I doing wrong?

Here is the screen shot enter image description here

Sharon Nathaniel
  • 1,467
  • 20
  • 28
  • 1
    I'd like to hear a real solution for this as well (the only answer as of now just states why it's not working.) `NSFastEnumeration` is a heavily-used protocol all over `NSFoundation` (`NSSet`, `NSHashTable`, `NSMapTable`, `NSPointerArray`, etc) and it feels redundant to extend all of those classes just to conform to `SequenceType`, when the same `for-in` construct was already supported for those classes in Objective-C. – John Estropia Sep 16 '14 at 15:08

6 Answers6

23

After a while poking around the swift framework files, I finally found this nice class called NSFastGenerator. NSSet and friends seem to be using the same Generator.

For ZBarSymbolSet, here's how you'd extend it to support for-in loops:

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

Update: Looks like Swift 2.0's protocol extensions fixed this for us!

John Estropia
  • 17,460
  • 4
  • 46
  • 50
  • 1
    That's brilliant, but Apple should do this by default for all their own NSFastEnumeration-conforming classes. As it is, I'm having to repeat this extension for every NSFastEnumeration class where I want to say `for...in`, which is nuts. – matt Oct 26 '14 at 20:13
  • Although Swift 2.0 supports protocol extensions, they cannot have an inheritance clause, and so the technique still appears to be limited to classes. – lukhnos Sep 22 '15 at 22:39
  • @lukhnos the NSFastEnumeration protocol is meant for old objective-c collections. You shouldn't be using it for structs; just use SequenceType. – John Estropia Sep 23 '15 at 00:13
3

Your defined class ZBarSymbolSet needs to implement the Swift SequenceType interface in order to be usable in for <identifier> in <sequence> syntax. The SequenceType interface is

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

and thus you see the mention of Generator as reported in your error message.

Also in the syntax:

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

the <identifer> is only in scope for <statements>. Thus your second use of symbol in the if will be out of scope and an error. One proper idiom would be:

var symbolFound : ZBarSymbol?

for symbol in result {
  symbolFound = symbol
  break
}

if symbolFound ...

If course, but the time ZBarSymbolSet implements SequenceType it would also implement CollectionType with subscript and thus the whole 'find the first element' code would be var symbol = result[0]

GoZoner
  • 67,920
  • 20
  • 95
  • 145
3

Here is John Estropia's answer for Swift 3:

extension ZBarSymbolSet: Sequence {

    public typealias Iterator = NSFastEnumerationIterator

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

}

Then you for-in loop would look like this:

for element in results {
    let symbol = element as! ZBarSymbol
    // ...
}

This answer could be improved by also adopting the IteratorProtocol so you can specify the element associated type as ZBarSymbol. I have not figured out how to do this yet.

keithbhunter
  • 12,258
  • 4
  • 33
  • 58
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 chauve
  • 582
  • 5
  • 8
  • Thanks Alok, but John's answer solved the issue that I was facing. I think your answer will help others figure out the complete picture. – Sharon Nathaniel Sep 19 '14 at 12:14
  • In Swift 2, I'm getting the error **Could not cast value of type '__NSArrayM' to 'ZBarSymbolSet'** at this line `for symbol in results as ZBarSymbolSet`. – Isuru Mar 16 '16 at 06:52
2

Here's a way to do it without writing an extension

    var iterator = NSFastEnumerationIterator(collection)
    while let element = iterator.next() {
        // use element
    }
skensell
  • 1,421
  • 12
  • 21
1

Also, if you know that all objects in your ZBarSymbolSet are ZBarSymbol objects (since ObjC doesn't enforce all objects being ZBarSymbol objects), you can do the following:

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)
    }
}

Now your for-loop will look like this:

for element in results {
    // element is a ZBarSymbol
}