Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-01-23

The GeneratorType and SequenceType protocols

It can be very useful to be able to iterate over the content of your objects. For this, Swift provides the SequenceType protocol, which in turn needs the GeneratorType protocol.

When you implement the SequenceType protocol you can iterate over the data in your object as follows:

    for x in objectOfMyClass {
        ... process x
    }

The SequenceType protocol is simple:

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

It needs just one function that returns an object which implements the GeneratorType protocol. It can be tempting to return "self" as the generator. And that will indeed work, ... a little. It is however dangerous  as you have no protection against multiple uses of the same generator, for example with nested loops or object access from multiple threads. A safer approach is to create a unique generator object on each call to the generate function. Still, even then, writing a safe generator can be a bit daunting as you need to think about mutations that could occur while you are iterating over your internal data.

The GeneratorType protocol is also very simple, you just have to implement one function:

protocol GeneratorType {
    typealias Element
    mutating func next() -> Element?
}

Here is an implementation that I think is relatively robust:

class MyDataClass {
    var myData = "data"
}

class MyClass: SequenceType {
    
    func values() -> Array<MyDataClass>? { return ... }
    
    struct MyClassGenerator: GeneratorType {
        
        typealias Element = MyDataClass
        
        // The object for which the generator generates
        let source: MyClass
        
        // The objects already delivered through the generator
        var sent: Array<MyDataClass> = []
        
        init(source: MyClass) {
            self.source = source
        }
        
        // The GeneratorType protocol
        mutating func next() -> Element? {
            
            // Only when the source has values to deliver
            if var values = source.values() {
                
                // Find a value that has not been sent already
                OUTER: for i in values {
                    
                    // Check if the value has not been sent already
                    for s in sent {
                        
                        // If it was sent, then try the next value
                        if i === s { continue OUTER }
                    }
                    // Found a value that was not sent yet
                    // Remember that it will be sent
                    sent.append(i)
                    
                    // Send it
                    return i
                }
            }
            // Nothing left to send
            return nil
        }
    }
    
    typealias Generator = MyClassGenerator
    func generate() -> Generator {
        return MyClassGenerator(source: self)
    }
}

This implementation keeps track of mutations that can occur in the source class and will respect these mutations on subsequent calls to "next()". Hence updates being made to a MyClass object will be respected. For example deleting MyDataClass objects that have been sent already will not upset the for loop, nor will removing MyDataClass objects that have not been sent yet result in "next()-ing" data that is not present anymore.

Happy coding ...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment