Reading bytes from packet
One things for certain... an array of tuples which is basically being used as a glorified dictionary is never going to suffice as an acceptable data model.  Moreover, an array of bytes isn't really that much useful than what we start with.

So what's clear, we need an actual data model.  So names and data types may need some twiddling, but here's what I started with:

class Information {
    let startTime: UInt32
    let duration: UInt32
    let temperature: UInt16
    let estimatedDistance: UInt16
    let knockCount: UInt8
    let reserved: UInt8

    // TODO: implement initializers
}


This is the model for our data.  This is the easiest way for the entire rest of our app to access the data.  This will be way easier to use.  

All that's left is getting from our data packet (&lt;4e5ba256 12190000 22001219 0700&gt;) to an actual instantiated Information object.

I know that we can get from an NSData object to an array of UInt8s, but I notice, we're going to need some UInt16s and some UInt32s.  So, I extended UInt16 and UInt32 to include some constructors for taking an array of bytes:

extension UInt16 {
    init?(bytes: [UInt8]) {
        if bytes.count != 2 {
            return nil
        }

        var value: UInt16 = 0
        for byte in bytes.reverse() {
            value = value &lt;&lt; 8
            value = value | UInt16(byte)
        }
        self = value
    }
}

extension UInt32 {
    init?(bytes: [UInt8]) {
        if bytes.count != 4 {
            return nil
        }

        var value: UInt32 = 0
        for byte in bytes.reverse() {
            value = value &lt;&lt; 8
            value = value | UInt32(byte)
        }
        self = value
    }
}


If it's not immediately obvious how this is useful, keep reading.  We'll see in a bit.

Now, what do we need to input and what do we need to get in return?  At the end of the day, we need an Information object, which is this class of six variously sized unsigned integers.  What we start with, however, is some raw data.  So ideally, we'd like to simply do something like this:

let myInformation = Information(data: data)


Right?  That'd be nice, eh?

So the end goal is to have an Information initializer which takes an NSData object.

Well, let's start with the simplest initializer for the most ideal case:

init(startTime: UInt32, duration: UInt32, temperature: UInt16, estimatedDistance: UInt16, knockCount: UInt8, reserved: UInt8) {
    self.startTime = startTime
    self.duration = duration
    self.temperature = temperature
    self.estimatedDistance = estimatedDistance
    self.knockCount = knockCount
    self.reserved = reserved
}


If we get the needed values exactly as we need them, we can just directly assign them as such.  This is just a member-wise initializer.

Let's rephrase that initializer in the form of UInt8 (or [UInt8]) though.  This next one will be marked as a failable convenience initializer.

convenience init?(startTimeBytes: [UInt8], durationBytes: [UInt8], temperatureBytes: [UInt8], estimatedDistanceBytes: [UInt8], knockCount: UInt8, reserved: UInt8) {
    if let
        startTime = UInt32(bytes: startTimeBytes),
        duration = UInt32(bytes: durationBytes),
        temperature = UInt16(bytes: temperatureBytes),
        estimatedDistance = UInt16(bytes: estimatedDistanceBytes) {
            self.init(startTime: startTime, duration: duration, temperature: temperature, estimatedDistance: estimatedDistance, knockCount: knockCount, reserved: reserved)
    } else {
        self.init(fail: true)
    }
}


(Don't worry about self.init(faile: true) right now.  I'll paste that implementation in a second.  For now, just note that it's saving me some boilerplate copy-pasta.)

We've got an if let trying to bind four variables using the UInt32 and UInt16 initializers we extended (which of course return optionals).  So if the passed in arrays are of the wrong size, this initializer will return nil, otherwise, it will return us the Information object we need.

So, what's left?  The next step up is the exact initializer that we want... the one that takes the NSData argument:

convenience init?(data: NSData) {
    if data.length == 14 {
        let allBytes = Array(UnsafeBufferPointer(start: UnsafePointer&lt;UInt8&gt;(data.bytes), count: data.length))
        self.init(startTimeBytes: Array(allBytes[0..&lt;4]), durationBytes: Array(allBytes[4..&lt;8]), temperatureBytes: Array(allBytes[8..&lt;10]), estimatedDistanceBytes: Array(allBytes[10..&lt;12]), knockCount: allBytes[12], reserved: allBytes[13])
    } else {
        self.init(fail: true)
    }
}


We optimize a little bit here by checking the data length (which is also preventing any array index out of bounds exceptions).  And then assuming the data is the right size, we call the previously mentioned convenience initializer.

For the sake of completeness, here's what self.init(fail: true) does:

private init?(fail: Bool = true) {
    self.startTime = 0
    self.duration = 0
    self.temperature = 0
    self.estimatedDistance = 0
    self.knockCount = 0
    self.reserved = 0
    if fail {
        return nil
    }
}


Because of the way Swift initializers work, having this initializer saves us from having to assign every property in every single initializer.  If we know any of our other failable initializers have entered a failure state, we just call this and we're done.



The code, start to finish:

import Foundation

extension UInt16 {
    init?(bytes: [UInt8]) {
        if bytes.count != 2 {
            return nil
        }

        var value: UInt16 = 0
        for byte in bytes.reverse() {
            value = value &lt;&lt; 8
            value = value | UInt16(byte)
        }
        self = value
    }
}

extension UInt32 {
    init?(bytes: [UInt8]) {
        if bytes.count != 4 {
            return nil
        }

        var value: UInt32 = 0
        for byte in bytes.reverse() {
            value = value &lt;&lt; 8
            value = value | UInt32(byte)
        }
        self = value
    }
}

let bytes: [UInt8] = [0x4e, 0x5b, 0xa2, 0x56, 0x12, 0x19, 0x00, 0x00, 0x22, 0x00, 0x12,0x19, 0x07, 0x00]
let data = NSData(bytes: bytes, length: bytes.count)

class Information {
    let startTime: UInt32
    let duration: UInt32
    let temperature: UInt16
    let estimatedDistance: UInt16
    let knockCount: UInt8
    let reserved: UInt8

    private init?(fail: Bool = true) {
        self.startTime = 0
        self.duration = 0
        self.temperature = 0
        self.estimatedDistance = 0
        self.knockCount = 0
        self.reserved = 0
        if fail {
            return nil
        }
    }

    init(startTime: UInt32, duration: UInt32, temperature: UInt16, estimatedDistance: UInt16, knockCount: UInt8, reserved: UInt8) {
        self.startTime = startTime
        self.duration = duration
        self.temperature = temperature
        self.estimatedDistance = estimatedDistance
        self.knockCount = knockCount
        self.reserved = reserved
    }

    convenience init?(startTimeBytes: [UInt8], durationBytes: [UInt8], temperatureBytes: [UInt8], estimatedDistanceBytes: [UInt8], knockCount: UInt8, reserved: UInt8) {
        if let
            startTime = UInt32(bytes: startTimeBytes),
            duration = UInt32(bytes: durationBytes),
            temperature = UInt16(bytes: temperatureBytes),
            estimatedDistance = UInt16(bytes: estimatedDistanceBytes) {
                self.init(startTime: startTime, duration: duration, temperature: temperature, estimatedDistance: estimatedDistance, knockCount: knockCount, reserved: reserved)
        } else {
            self.init(fail: true)
        }
    }

    convenience init?(data: NSData) {
        if data.length == 14 {
            let allBytes = Array(UnsafeBufferPointer(start: UnsafePointer&lt;UInt8&gt;(data.bytes), count: data.length))
            self.init(startTimeBytes: Array(allBytes[0..&lt;4]), durationBytes: Array(allBytes[4..&lt;8]), temperatureBytes: Array(allBytes[8..&lt;10]), estimatedDistanceBytes: Array(allBytes[10..&lt;12]), knockCount: allBytes[12], reserved: allBytes[13])
        } else {
            self.init(fail: true)
        }
    }
}

func stringFromTimeInterval(interval:NSTimeInterval) -&gt; String {
    let ti = NSInteger(interval)

    let ms = Int((interval % 1) * 1000)

    let seconds = ti % 60
    let minutes = (ti / 60) % 60
    let hours = (ti / 3600)

    return NSString(format: "%0.2d:%0.2d:%0.2d.%0.3d",hours,minutes,seconds,ms) as String
}

let i = Information(data: data)!
let timestamp = NSDate(timeIntervalSince1970: NSTimeInterval(i.startTime))
let duration = stringFromTimeInterval(NSTimeInterval(i.duration))
let temperature = i.temperature
let estimatedDistance = i.estimatedDistance
let knockCount = i.knockCount
let reserved = i.reserved


And this is the result I get for the given data: