In my app (only relevant code shown), I have a class Test
with a property
var location: CLLocation
I archive it using
public func encode(with aCoder: NSCoder) {
aCoder.encode(location, forKey: "location")
}
And it is unarchived using
required convenience public init?(coder aDecoder: NSCoder) {
let unarchivedLocation = aDecoder.decodeObject(forKey: "location") as! CLLocation
self.init(location: unarchivedLocation)
}
Unit testing is done using
func test_archiningUnarchiving() {
// given
let location = CLLocation.init(latitude: 0.0, longitude: 0.0)
let test = Test(location: location)
// when
let data = NSKeyedArchiver.archivedData(withRootObject: test)
let unarchivedTest = NSKeyedUnarchiver.unarchiveObject(with: data) as? Test
// then
XCTAssertEqual(unarchivedTest!.location, location, "location was not correctly unarchived")
}
This test fails:
XCTAssertEqual failed: ("<+0.00000000,+0.00000000> +/- 0.00m (speed -1.00 mps / course -1.00) @ 1/19/18, 3:30:50 PM Central European Standard Time") is not equal to ("<+0.00000000,+0.00000000> +/- 0.00m (speed -1.00 mps / course -1.00) @ 1/19/18, 3:30:50 PM Central European Standard Time") - location was not correctly unarchived
The log shows twice exactly the same data for the original and the unarchived location.
Any idea what could have gone wrong anyway??
The problem is not archiving, but rather the equality test. If you compare two different CLLocation
instances, even if they're identical, it will always return false
.
Bottom line, any NSObject
subclass that doesn't explicitly implement isEqual:
(such as is the case with CLLocation
) will experience this behavior.
The Using Swift with Cocoa and Objective-C: Interacting with Objective-C APIs says:
Swift provides default implementations of the
==
and===
operators and adopts theEquatable
protocol for objects that derive from theNSObject
class. The default implementation of the==
operator invokes theisEqual:
method ... You should not override the equality or identity operators for types imported from Objective-C.
And, the Concepts in Objective-C Programming: Introspection tells us:
The default
NSObject
implementation ofisEqual:
simply checks for pointer equality.
Personally, I wish that NSObject
subclasses didn't automatically inherit isEqual:
, but it is what it is.
Bottom line, don't attempt to test for equality of NSObject
subclasses unless you know it has properly implemented isEqual:
override. If you want, write your own method, e.g. isEqual(to location: CLLocation)
(but not isEqual(_:)
) that performs memberwise comparison of two CLLocation
objects.