featherless
software design

Enumerate MIDIPacketList in Swift: Part 1

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 Sequence

Treating 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 implementation

Someone 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.

References