Search code examples
swiftstructnsuserdefaultsnsdata

Swift UnsafePointer<T>(data.bytes).memory crash under some (but not all) circumstances


I have the following struct:

struct Identity {
    var id: Int
    var createdAt: NSDate
    var string: String
    var apnsToken: String
}

Over the course of my application's execution, instances (?) of this struct are turned into NSData (using the following code) and stored in NSUserDefaults:

var id = Identity(id: 0, createdAt: NSDate(), string: "string", apnsToken: "<apns-token-here>")
var data = NSData(bytesNoCopy: &id, length: sizeof(Identity),freeWhenDone:false)

When I try to get a struct from the NSData instance, it crashes with an EXC_BAD_ACCESS (it's a code 1):

var id = UnsafePointer<Identity>(userDefaultsData.bytes).memory

However, this only happens when I get the NSData instance from NSUserDefaults. If I do something like below, it works without a crash.

var id = Identity(id: 0, createdAt: NSDate(), string: "string", apnsToken: "<apns-token-here>")
var data = NSData(bytesNoCopy: &id, length: sizeof(Identity),freeWhenDone:false)
var idPrime = UnsafePointer<Identity>(data.bytes).memory

The assembly code dumped by the EXC_BAD_ACCESS is somewhere halfway through objc_retain, after an and instruction.

UPDATE:

I wasn't completely honest. The data is retrieved from the keychain in ObjC, bridge_transfer cast to an NSData from a CF data object. The CF object comes from SecItemCopy() as an out param. I thought NSUserDefaults would be more relatable.


Solution

  • This is because this line:

    var data = NSData(bytesNoCopy: &id, length: sizeof(Identity),freeWhenDone:false)
    

    will not render the Strings (or any other type that allocates its own memory, like an array) into byte form. Instead all it will do is serialize the pointer to the string’s memory into the NSData bytes.

    This obviously is going to lead to explosions if the memory is no longer there. Which is why it might seem to be working when you do it all in one go, but not when you store to user defaults and then, later or maybe even in a different process, get it back out.

    Instead, you’ll need to do something like store the strings into their own NSData objects (say with NSData(base64EncodedString:options:)) and then store that too.