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.

JuJoDi
  • 14,627
  • 23
  • 80
  • 126

1 Answers1

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.

Bill
  • 44,502
  • 24
  • 122
  • 213
  • This project was written entirely in Swift, there were never Objective-C objects being encoded or decoded, interesting though – JuJoDi Jul 13 '14 at 15:37
  • Can you post any or all of the Stopwatch source? – Bill Jul 13 '14 at 15:41
  • Sure - https://github.com/justinjdickow/stopwatch_swift_implementation/blob/master/Stopwatch.swift – JuJoDi Jul 13 '14 at 15:53
  • Out of curiosity, did you try giving it an objc name? – Bill Jul 13 '14 at 15:55
  • 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_INSTRUCTION – JuJoDi Jul 13 '14 at 16:05
  • What do you mean by `cannot decode object of class (_whateverStopwatch)`? Is it showing Swift name mangling? – Bill Jul 14 '14 at 12:35
  • "'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtC11stopwatches9Stopwatch)'" – JuJoDi Jul 14 '14 at 12:46
  • 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? – Bill Jul 14 '14 at 12:48
  • 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")` – JuJoDi Jul 14 '14 at 13:02
  • 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. – Bill Jul 14 '14 at 13:25
  • 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 sense – JuJoDi Jul 14 '14 at 13:41