Add "for in" support to iterate over Swift custom classes

@Matt Gibson is correct. However, I would like to add more information for future reference.

From Advanced Swift:

This code:

for x in someSequence {
    ...
}

Is converted into this:

var __g = someSequence.generate()
while let x = __g.next() {
    ...
}

Therefore, one must adopt Sequence, which gives the class generate() and next(). Here are these protocols:

protocol Generator {
    typealias Element
    mutating func next() -> Element?
}
protocol Sequence {
    typealias GeneratorType : Generator
    func generate() -> GeneratorType
}

All of the above answers can be a little tricky. If you have an array in your class, which you want to iterate over (like in @Lee Whitney's answer), there's a much simpler way to implement it. You have the following class, CustomClass:

class CustomClass: SequenceType {
    let array: [String]

    init(array: [String]) {
        self.array = array
    }

    func generate() -> IndexingGenerator<[String]> {
        return array.generate()
    }
}

Simple as that. Tested to work in the latest Xcode version (6.1 at the time of writing), and iOS 8.1.2. This code should be stable through future versions, though.

P.S. With generics, you can easily do your own Array replica by following this pattern, and only implement the methods which you want.


Say you have a class "Cars" that you want to be able to iterate over using a for..in loop:

let cars = Cars()

for car in cars {
    println(car.name)
}

The simplest way is to use AnyGenerator with the classes like this:

class Car {
    var name : String
    init(name : String) {
        self.name = name
    }
}

class Cars : SequenceType {

    var carList : [Car] = []

    func generate() -> AnyGenerator<Car> {
        // keep the index of the next car in the iteration
        var nextIndex = carList.count-1

        // Construct a AnyGenerator<Car> instance, passing a closure that returns the next car in the iteration
        return anyGenerator {
            if (nextIndex < 0) {
                return nil
            }
            return self.carList[nextIndex--]
        }
    }
}

To try a complete working sample add the two classes above and then try to use them like this, adding a couple of test items:

    let cars = Cars()

    cars.carList.append(Car(name: "Honda"))
    cars.carList.append(Car(name: "Toyota"))

    for car in cars {
        println(car.name)
    }


That's it, simple.

More info: http://lillylabs.no/2014/09/30/make-iterable-swift-collection-type-sequencetype

Tags:

Swift