Search code examples
iosswiftunsafe-pointers

EXC_ARM_DA_ALIGN when reading from NSData in Swift


I have the following class:

class RawDataArray {
    var data: NSData!

    init(filePath: String) {
        data = NSData(contentsOfFile: filePath)
    }

    func read<T>(offset: Int) -> T {
        return UnsafePointer<T>(data.bytes + offset).memory
    }
}

which I use in my iOS app to read from a binary file with a custom format. For example, to read an Int at offset 5, I use:

let foo = rawData.read(5) as Int

This works in the simulator, on my iPad Air, and has passed the review for beta testing. However, my external testers have iPad 2s and 4s, and they are getting EXC_ARM_DA_ALIGN errors.

I cannot change the structure of the input file. Is there any way to fix the read function to make sure the objects are built from properly aligned memory locations?


Solution

  • Yes, reading unaligned data can fail on some architectures. A safe method is to copy the bytes into a (properly aligned) variable.

    So the first idea would be to write a generic function:

    func read<T>(offset: Int) -> T {
        var value : T
        memcpy(&value, data.bytes + offset, UInt(sizeof(T)))
        return value
    }
    

    However, this does not compile, because the variable value must be initialized before being used, and there is no default constructor that can be used to initialize a complete generic variable.

    If we restrict the method to types which can be initialized with 0 then we get this:

    func read<T : IntegerLiteralConvertible>(offset: Int) -> T {
        var value : T = 0
        memcpy(&value, data.bytes + offset, UInt(sizeof(T)))
        return value
    }
    

    This works for all integer and floating point types.

    For the general case, one could use the idea from https://stackoverflow.com/a/24335355/1187415:

    func read<T>(offset: Int) -> T {
        // Allocate space:
        let ptr = UnsafeMutablePointer<T>.alloc(1)
        // Copy data:
        memcpy(ptr, data.bytes + offset, UInt(sizeof(T)))
        // Retrieve the value as a new variable: 
        var item = ptr.move()
        // Free the allocated space
        ptr.dealloc(1)
        return item
    }
    

    Swift allows to overload functions with different return types, therefore you can actually define both methods, and the compiler will automatically choose the more restrictive one.