Published: Oct 5, 2015
In this post we’re going to look at how we can use Swift 2’s SequenceType and GeneratorType to enumerate the packets of a MIDIPacketList.
A SequenceType is “a type that can be iterated with a for…in loop.”
A GeneratorType “encapsulates iteration state and interface for iteration over a sequence.”
But before we dive into the implementation, let’s understand how MIDI packets are represented in Swift.
#
What is a packet list?CoreMIDI transports messages via MIDIPacketList, which is essentially a linked list of MIDIPacket structs in that its packets can only be sequentially iterated over.
// Extracted from the CoreMIDI Swift framework public struct MIDIPacketList { public var numPackets: UInt32 public var packet: (MIDIPacket) public init() public init(numPackets: UInt32, packet: (MIDIPacket)) }
Notice that packet
is a singular MIDIPacket, not an array like you might expect. This is a side effect of the Objective-C struct from which this Swift struct was generated:
// Extracted from the CoreMIDI Objective-C framework header struct MIDIPacketList { UInt32 numPackets; MIDIPacket packet[1]; };
So how do we iterate over the packets? The answer was introduced in Swift 2: MIDIPacketNext.
MIDIPacketNext is an Objective-C macro made available in Swift 2. Contrary to what the docs imply, MIDIPacketNext works on pre-iOS 9 devices running Swift thanks to the embedded runtime.
A C implementation might look like this:
MIDIPacket *packet = &packetList->packet[0]; for (int i = 0; i < packetList->numPackets; ++i) { ... packet = MIDIPacketNext(packet); }
But using Swift extensions we’re going to allow enumeration like this:
let packetList: MIDIPacketList for packet in packetList { ... }
#
MIDIPacketList as a SequenceTreating MIDIPacketList like a sequence is easy with Swift extensions:
extension MIDIPacketList: SequenceType { }
Why SequenceType instead of CollectionType?
CollectionType requires subscript access at arbitrary indexes. This isn’t needed when processing a stream of MIDI packets. SequenceType is a more accurate model of how MIDIPackets are consumed. Learn more.
SequenceType requires two things for conformity:
a Generator
typealias, and
an implementation of func generate() -> Generator
.
#
Classic implementationSomeone coming from Objective-C has been trained to see a protocol and mechanically start implementing its required methods. In this case that would entail defining the Generator type:
public typealias Generator = MIDIPacketListGenerator
and implementing the generate method:
public func generate() -> Generator { return Generator(packetList: self) }
MIDIPacketListGenerator must be a GeneratorType, so we’d implement the struct accordingly:
public struct MIDIPacketListGenerator: GeneratorType { public typealias Element = MIDIPacket public mutating func next() -> Element? { // iterator logic } }
With a working implementation looking something like this:
public struct MIDIPacketListGenerator : GeneratorType { public typealias Element = MIDIPacket init(packetList: MIDIPacketList) { let ptr = UnsafeMutablePointer<MIDIPacket>.alloc(1) ptr.initialize(packetList.packet) self.packet = ptr self.count = packetList.numPackets } public mutating func next() -> Element? { guard self.packet != nil && self.index < self.count else { return nil } let lastPacket = self.packet! self.packet = MIDIPacketNext(self.packet!) self.index++ return lastPacket.memory } // Extracted packet list info var count: UInt32 var index: UInt32 = 0 // Iteration state var packet: UnsafeMutablePointer<MIDIPacket>? }
But this is a pretty long solution in Swift, a language with type inference and a variety of other useful tools. In part 2 of this post we’ll reduce the size of this implementation using Swift’s AnyGenerator type. Read Part 2 now.
View the full implementation of this approach.
#
ReferencesSwift 2 and CoreMIDI by Gene De Lisa