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
- Overview of Swift collection protocols on NSHipster
- Jacob’s great post about implementing the Fibonacci Sequence using SequenceType
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.