33

As we know, we can use the for..in loop to iterate across Arrays or Dictionaries. However, I would like to iterate over my own CustomClass like this:

for i in CustomClass {
    someFunction(i)
}

What operations/protocols does CustomClass have to support for this to be possible?

whitneyland
  • 10,632
  • 9
  • 60
  • 68
yizzlez
  • 8,757
  • 4
  • 29
  • 44

6 Answers6

32

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

Mazyod
  • 22,319
  • 10
  • 92
  • 157
whitneyland
  • 10,632
  • 9
  • 60
  • 68
  • 1
    Now that we have protocol extensions in Swift 2.0, this works a bit differently. Instead of conforming to SequenceType, one should simply subclass AnyGenerator and override the next() method to return the proper item in the iteration (in this case, next() –> Car?) – metatheoretic Jun 25 '15 at 01:42
  • Updated to Swift 2 syntax. it's a little premature, since that's still beta, but the answer will be correct longer. See the history if you need Swift 1.2. – Rob Napier Jul 30 '15 at 17:33
  • @metatheoretic, I don't believe there is any reason to subclass `AnyGenerator`. Where are you getting that? `SequenceType` is still the protocol for `for...in`. – Rob Napier Jul 30 '15 at 17:33
  • Here's an alternate take which iterates in the forward direction rather than the reverse direction (also, uses guard, etc): https://gist.github.com/cellularmitosis/64d4a2d2d6dfa33916fc – Jason Pepas Jan 19 '16 at 19:27
  • Needs update for Swift 2.2 http://stackoverflow.com/questions/36512156/using-anygenerator-with-swift-2-2-for-in-loop-support-for-custom-classes/36512213#36512213 – Suragch Apr 09 '16 at 02:58
  • It's worth pointing out that the use of `anyGenerator` is not a typo. There is the `AnyGenerator<>` class, and there is the `anyGenerator()` factory function that returns an `AnyGenerator<>` class with the trailing block after the call to `anyGenerator()` becoming the `next()` method of the returned `AnyGenerator<>` instance. That melted my brain for a while because the documentation for the function and the class aren't in the same place and the class docs don't mention the factory function. This might also be why @metatheoretic was led to believe `AnyGenerator<>` needed to be subclassed. – Omegaman May 11 '16 at 02:47
  • [Documentation](http://swiftdoc.org/v2.2/func/anyGenerator/#func-anygenerator-element_-element) for `anyGenerator`. I did not find its counterpart in Swift 3. – Franklin Yu Jul 22 '16 at 00:37
  • Oh, in Swift 3 we just call [`AnyIterator`](http://swiftdoc.org/v3.0/type/AnyIterator/#init_-element). – Franklin Yu Jul 22 '16 at 01:10
11

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.

ArVID220u
  • 374
  • 3
  • 10
  • I was about to post exactly this. If your intention is to make your class iterable over a field that conforms to Generator, you can just directly expose the field's Generator. – Alexander Dec 17 '15 at 21:59
4

@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
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
yizzlez
  • 8,757
  • 4
  • 29
  • 44
  • `For in` statements loops over `GeneratorType`, which it can optionally retrieve from a `SequenceType`. So technically you need to supply it with a `GeneratorType` *or* a `SequenceType`. – Palimondo Dec 18 '14 at 23:37
2

That would be the SequenceType protocol, and its related Generator protocol.

The SequenceType protocol says that the class must implement generate(), which returns something that conforms to the Generator protocol, which is the bit that does the actual work; the Generator protocol is the one with the all-important next() method.

There's an example of implementing it to allow for..in in the WWDC 2014 video "Advanced Swift" (in the generics example "A Simple Generic Stack", starting around slide 183.)

The basic info on which protocol to implement for for..in is in the Statements section of the documentation, which gives a brief overview of SequenceType and Generator

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
0

NOTE AnyGenerator and SequenceType are old types that do not exist in recent versions. You need to implement Sequence protocol now.

There are two ways to implement Sequence.

  1. Conform to Sequence, IteratorProtocol at the same time by just implementing next() method. This approach is the simplest and there is an example in the headers. See Sequence.swift. Implementing both protocols at the same time could be non-realistic or not fulfill your needs. (It might prevent two different instances to be iterated at the same time, etc)

  2. Conform to Sequence and return an object that implements IteratorProtocol. I think this is the most common case in real world classes (when things become a bit complicated, not Hello Worlds). There is also an example in the Sequence.swift

Below is an example of approach 2. An custom class (Linked List) which is iterable:

/// Linked List Node:
public class LinkedListNode <T> {

    public internal(set) var value: T

    public internal(set) var next: LinkedListNode<T>?

    internal init(_ value: T) {
        self.value = value
        self.next = nil
    }
}

/// Linked List with append method only.
public class LinkedList<T> {

    public internal(set) var first: LinkedListNode<T>? = nil

    public internal(set) var last: LinkedListNode<T>? = nil

    /// Appends a new node.
    public func append(_ value: T) {
        if first == nil {
            first = LinkedListNode(value)
            last = first
        } else {
            last.next = LinkedListNode(value)
            last = last.next
        }
    }
}

Finally the Sequence implementation

/// Sequence protocol adoption. It allows `for ... in` and a bunch of other methods too.
extension LinkedList: Sequence {

    /// Iterator implementation
    public class Iterator<T>: IteratorProtocol {

        /// Maintain a ref to current element so next element can be reached
        var cur: LinkedListNode<T>?

        /// IteratorProtocol protocol requirement
        public func next() -> T? {
            let res = cur?.value
            cur = cur?.next
            return res
        }
    }

    /// Sequence protocol requirement
    public func makeIterator() -> Iterator<T> {
        let g = LinkedList.Iterator()
        g.cur = first
        return g
    }
}

Usage:

let linkedList = LinkedList<Int>()
linkedList.append(3)
linkedList.append(6)
linkedList.append(9)
linkedList.append(12)
for element in linkedList {
    print(element)
}

let odds = linkedList.filter { return $0 % 2 == 0 }
print(odds)
nacho4d
  • 43,720
  • 45
  • 157
  • 240
-1

The accepted answer is correct, and up until recently was the accepted way to address this. However, given the introduction of Protocol Extensions in Swift 2.0, rather than conformance to SequenceTypeand implementing func generate() -> GeneratorOf<Car> there is now an abstract base class that handles the implementation of this functionality for you called AnyGenerator<T> (see Apple docs) since GeneratorOf<T> no longer exists.

What this means is that you can simply subclass this abstract base class, and by doing do, inherit all of the functionality of the aforementioned protocol conformance:

class Cars: AnyGenerator<Car> {

     private var carList = [Car]()
     private var currentIndex:Int

     ...

}

One then need only override the next() method declared by the GeneratorType protocol (which AnyGenerator<T> also conforms to) in order to define the desired iteration behavior:

class Cars: AnyGenerator<Car> {

     private var carList = [Car]()
     private var currentIndex:Int

     override func next() -> Car? {

         if (currentIndex < self.carList.count) {
            currentIndex++
            return self.carList[currentIndex-1]
         } else {
            currentIndex = 0;
            return nil
         }
     }

}
metatheoretic
  • 1,082
  • 9
  • 13
  • You say: "there is now an abstract base class that handles the implementation of this functionality for you called `AnyGenerator`". I don't see that `AnyGenerator` is doing anything for you. I don't understand why they made the change but didn't provide any guidance or explanation. – Michael Welch Jul 21 '15 at 02:46
  • I don't believe the above is true. There's no need to subclass `AnyGenerator` for this. See edits to accepted answer. They just renamed GeneratorOf to AnyGenerator, and moved GeneratorOf(next:) to a new function anyGenerator() – Rob Napier Jul 30 '15 at 17:53
  • This is absolutely wrong. AnyGenerator is a type-erased version of Generator, for non-generic use cases. It is not meant to be a base class, and the example here is something you never want to do. – Andrey Tarantsov Jun 11 '16 at 14:35