Enumerate Midipacketlist In Swift Part 2
by
Enumerate MIDIPacketList in Swift: Part 2
This is the conclusion of Enumerate MIDIPacketList in Swift.
In Part 1 we examined the structure of MIDIPacketList and built an Objective-C-influenced implementation of SequenceType. In this post we’ll look at Swift 2’s AnyGenerator type to achieve the same effect — but more concisely — thanks to Swift type inference and closures.
AnyGenerator
Swift 2’s AnyGenerator type - and corresponding anyGenerator method - allow us to create inline generators. This removes the need to create a separate Generator class and — paired with type inference — allows us to remove a lot of boilerplate. Let’s re-examine our MIDIPacketList extension.
The essential compilable code for a SequenceType built with AnyGenerator looks like this:
extension MIDIPacketList: SequenceType {
public func generate() -> AnyGenerator<MIDIPacket> {
// TODO: Local state...
return anyGenerator({
// TODO: return nil if no additional elements
// TODO: Iterator logic
return nil // TODO: return the next sequence item
})
}
}
- Swift is able to infer the Generator type based on the return value of
generate()
. - A Generator returns nil to indicate that it has no more elements to generate.
The Generator in the code above will not generate anything. Let’s fill in the implementation.
Step 1: Iterator state
First we define the generator’s iterator.
public func generate() -> AnyGenerator<MIDIPacket> {
var iterator: MIDIPacket?
var nextIndex: UInt32 = 0
iterator
stores the last packet we returned from the generator. Its initial nil value indicates that we haven’t enumerated a packet yet.nextIndex
allows us to know once we’ve consumed all packets. This is required because MIDIPacketNext advances packet pointers by a fixed amount on each invocation, eventually pointing to memory not allocated for packets. nextPacket alone is not a reliable signal for having consumed all packets.
Step 2: Iterator logic
Next we define the logic that will advance the iterator on each call of the generator.
return anyGenerator {
if iterator == nil {
iterator = self.packet
} else {
iterator = withUnsafePointer(&iterator!) { MIDIPacketNext($0).memory }
}
return iterator
}
iterator
’s nil state allows us to return self.packet initially.- Each subsequent call advances
iterator
with MIDIPacketNext. - MIDIPacketNext accepts and returns an UnsafePointer. withUnsafePointer is a helpful way to transform between MIDIPacket and UnsafePointer
, avoiding the need to initialize an [UnsafeMutablePointer] ourselves. - The
$0
is shorthand for the closure’s first argument. - The enclosing
()
for the anyGenerator function has been removed because they’re not needed when the only argument is a closure.
Step 3: Iterator termination
The final implementation advances and checks nextIndex
against self.numPackets
before iterating in order to avoid accessing invalid memory.
public func generate() -> AnyGenerator<MIDIPacket> {
var iterator: MIDIPacket?
var nextIndex: UInt32 = 0
return anyGenerator {
if nextIndex++ >= self.numPackets { return nil }
if iterator == nil {
iterator = self.packet
} else {
iterator = withUnsafePointer(&iterator!) { MIDIPacketNext($0).memory }
}
return iterator
}
}
Addendum
This series went through multiple iterations as I learned more Swift language features. Check out the revision history of this entry’s code.
tags: