kelan.io

Swift - Adopting SequenceType

Here’s an example of implementing SequenceType in Swift (1.2).

Here is the existing Objective-C class I’m working with (well, a simplification thereof, written in Swift).

class MyStringCollection: NSObject {
    private let strings: [String]

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

    func count() -> Int {
        return strings.count
    }

    func stringAtIndex(index: Int) -> String?  {
        return strings[index]
    }

    func indexOfString(string: String) -> Int {
        return find(strings, string) ?? NSNotFound
    }
}

Here is what it looks like to use it:

// Example of using it:
let stringCollection = MyStringCollection(strings: [ "a", "b", "c", "d", ])
stringCollection.count()                    // => 4
stringCollection.stringAtIndex(2)           // => "c"
stringCollection.indexOfString("b")         // => 1
stringCollection.indexOfString("notfound")  // => 9223372036854775807

But, we can’t enumerate it (yet).

for (i, string) in enumerate(stringCollection) {
    println("string \(i): \(string)")        <-- error: Cannot invoke 'enumerate' with an argument list of type '(MyStringCollection)'
}

Enter SequenceType

The error message above wasn’t terribly helpful, but cmd-clicking on enumerate(), we see that it wants the argument to be SequenceType:

func enumerate<Seq : SequenceType>(base: Seq) -> EnumerateSequence<Seq>

So, let’s make our above class adopt SequenceType.

extension MyStringCollection: SequenceType {
    typealias Generator = MyStringCollectionGenerator

    // This is the requirement of SequenceType
    // It will be called once when we start enumerating, and returns a Generator,
    // which holds state about where we are in the sequence, and can return the
    // next item.
    func generate() -> MyStringCollectionGenerator {
        return MyStringCollectionGenerator(currentIndex: 0, collection: self)
    }

    // Here is the custom generator we return above.
    // It just has a reference to the original collection, and an integer
    // pointing to the current position in that collection.
    struct MyStringCollectionGenerator: GeneratorType {
        typealias Element = String

        var currentIndex: Int
        let collection: MyStringCollection

        // This is the requirement of GeneratorType
        mutating func next() -> Element?  {
            if currentIndex >= collection.count() {
                return nil
            }
            return collection.stringAtIndex(currentIndex++)
        }
    }
}

And, now we can enumerate() it.

for (i, string) in enumerate(stringCollection) {
    println("string \(i): \(string)")
}

Prints out:

string 0: a
string 1: b
string 2: c
string 3: d

Further Reading

Thanks

As usual, thanks to Jacob, for his help with this – both explaining this to me a few months ago at work, and reviewing this post.