NSKeyedArchiver unarchiveObjectWithFile crashes with EXC_BAD_INSTRUCTION
Asked Answered
P

1

7

I have the following code, used to get the path of an object that has been archived

let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let path = paths[0] as String
let archivePath = path.stringByAppendingString("archivePath")

When I run this code, it crashes at the NSSearchPathForDirectoriesInDomains call with lldb showing

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

In Xcode's Variables View I see the path String set as I would expect. What is the proper way to get a user directory in Swift for archiving/unarchiving objects?

Update:

It appears this is actually crashing on my use of the NSKeyedUnarchiver:

stopwatches = NSKeyedUnarchiver.unarchiveObjectWithFile(archivePath) as Stopwatch []

Stopwatch is a class that implements NSCoding, stopwatches is the datasource (an array of Stopwatches) owned by the view doing the unarchiving.

Update 2:

The object graph being archived is an array of Stopwatches. NSCoding is implemented as follows:

func encodeWithCoder(aCoder: NSCoder!) {
    aCoder.encodeBool(self.started, forKey: "started")
    aCoder.encodeBool(self.paused, forKey: "paused")
    aCoder.encodeObject(self.startTime, forKey: "startTime")
    aCoder.encodeObject(self.pauseTime, forKey: "pauseTime")
    aCoder.encodeInteger(self.id, forKey: "id")
}

init(coder aDecoder: NSCoder!) {
    self.started = aDecoder.decodeBoolForKey("started")
    self.paused = aDecoder.decodeBoolForKey("paused")
    self.startTime = aDecoder.decodeObjectForKey("startTime") as NSDate
    self.pauseTime = aDecoder.decodeObjectForKey("pauseTime") as NSDate
    self.id = aDecoder.decodeIntegerForKey("id")
    super.init()
}

Update 3: With expandTilde set to true my path is /Users/Justin/Library/Developer/CoreSimulator/Devices/FF808CCD-709F-408D-9416-E‌​E47B306309D/data/Containers/Data/Application/B39CCB84-F335-4B70-B732-5C3C26B4F6AC‌​/Documents/ArchivePath

If I set expandTilde to false, I don't get the crash, but the file is not archived and unarchived, and the path is @"~/Documents/ArchivePath"

Deleting the Application folder causes the first launch of the application to not crash, but does not allow it to reopen afterwards. Also, after deleting the application folder, I am now able to read the archive path in lldb rather than having to println it.

Pyrenees answered 2/7, 2014 at 18:46 Comment(12)
Works without problems in my iOS Simulator. But stringByAppendingString() should be pathByAppendingPathComponent().Auschwitz
@MartinR updated with new infoPyrenees
Can you show us what is archivePath that is being passed in?Veld
@JackWu When I use println it's /Users/Justin/Library/Developer/CoreSimulator/Devices/FF808CCD-709F-408D-9416-EE47B306309D/data/Containers/Data/Application/B39CCB84-F335-4B70-B732-5C3C26B4F6AC/Documents/ArchivePath, but if I try to print the description using lldb I get Printing description of ArchivePath: (String) ArchivePath = <variable not available>Pyrenees
Interesting, when you archive it, is it archived from a Stopwatch[] variable? Or is it an NSArray?Veld
I archive using NSKeyedArchiver.archiveRootObject(self.stopwatches, toFile: ArchivePath) and self.stopwatches is defined in the class as var stopwatches: Stopwatch []Pyrenees
does it help if you write NSString instead of String?Chide
shouldn't you call super.init() in your init method?Chide
@Chide no it does not; yes but it doesn't make a difference for this question.Pyrenees
What type is Stopwatch derived from?Tampon
@MattGibson It's class Stopwatch: NSObject, NSCoding {Pyrenees
And your stopwatches is just a Stopwatch[]?Tampon
S
8

I experienced a similar problem and it had to do with Swift's name mangling. If you're reading from a file that was generated from encoded Objective-C objects and each object had the Objective-C class name Stopwatch, you won't be able to directly decode that into Swift objects, because Swift performs name mangling on class and symbol names. What you see as Stopwatch in Swift code is internally something like T23323234_Stopwatch_3242.

To get around it, specify that your Swift class should be exported with a specific Objective-C class name, e.g.:

@objc(Stopwatch) class Stopwatch {
  ...
}

where Stopwatch matches the class name of the Objective-C objects you've archived.

Edit: In the project code you linked in the comments, there's a second problem here, i.e. trying to encode/decode Swift-specific features. Only Objective-C objects can be archived and unarchived. Sadly, structs, generics, tuples and optionals are not Objective-C compatible and can't work with NSCoding. The best way to work around this is to encode Swift types as NSDictionary instances, e.g. (untested):

let encodedLaps = self.laps.map { lap => ["start": lap.start, "end": lap.end] }
aCoder.encodeObject(encodedLaps, forKey: "laps")

Edit 2: Contrary to what I wrote above, this isn't limited to just reading Objective-C instances from Swift. It appears that any usage of NSCoding requires an Objective-C name for your class. I would imagine this is because Swift name-mangling can change between different runs of the compiler.

Supplejack answered 13/7, 2014 at 2:56 Comment(11)
This project was written entirely in Swift, there were never Objective-C objects being encoded or decoded, interesting thoughPyrenees
Can you post any or all of the Stopwatch source?Supplejack
Sure - github.com/justinjdickow/stopwatch_swift_implementation/blob/…Pyrenees
Out of curiosity, did you try giving it an objc name?Supplejack
When I use @objc(Stopwatch) class Stopwatch... (using the quotes didn't compile), I get "cannot decode object of class (_whateverStopwatch). Also it looks like with Beta 3 the failure is now at aCoder.encodeObject(self.laps, forKey: "laps"), but it's the same EXC_BAD_INSTRUCTIONPyrenees
What do you mean by cannot decode object of class (_whateverStopwatch)? Is it showing Swift name mangling?Supplejack
"'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtC11stopwatches9Stopwatch)'"Pyrenees
So, the @objc attribute should make it impossible for you to see Swift-mangled names there. Try once again @objc(Stopwatch), then delete the object file saved from the last time, then run your program again. I think your program is reading object data generated under Swift naming. Do you know what I mean?Supplejack
Yes, so I reset the data, which did get me passed the "cannot decode object of class" error, but it error'd out again at aCoder.encodeObject(self.laps, forKey: "laps")Pyrenees
You won't be able to encode/decode Swift-specific language features like generic arrays and tuples. That's the problem. If you want to use NSCoding to archive/unarchive this data, you'll need to convert it to fully Objective-C compatible data first. If you comment out the laps encode/decode lines, you shouldn't encounter any errors.Supplejack
Thanks, can you add that to your answer :D I had a todo to make laps a struct instead of a tuple anyway, but it makes sensePyrenees

© 2022 - 2024 — McMap. All rights reserved.