diff --git a/Foundation/NSMeasurement.swift b/Foundation/NSMeasurement.swift index 35233833cc..7f0f1742c7 100644 --- a/Foundation/NSMeasurement.swift +++ b/Foundation/NSMeasurement.swift @@ -81,9 +81,39 @@ open class NSMeasurement : NSObject, NSCopying, NSSecureCoding { open class var supportsSecureCoding: Bool { return true } - open func encode(with aCoder: NSCoder) { NSUnimplemented() } + private enum NSCodingKeys { + static let value = "NS.value" + static let unit = "NS.unit" + } - public required init?(coder aDecoder: NSCoder) { NSUnimplemented() } + public required init?(coder aDecoder: NSCoder) { + let value = aDecoder.decodeDouble(forKey: NSCodingKeys.value) + guard let unit = aDecoder.decodeObject(of: Unit.self, forKey: NSCodingKeys.unit) else { + aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: CocoaError.coderReadCorrupt.rawValue, userInfo: [NSLocalizedDescriptionKey: "Unit class object has been corrupted!"])) + return nil + } + + self.doubleValue = value + self.unit = unit + } + + open func encode(with aCoder: NSCoder) { + guard aCoder.allowsKeyedCoding else { + fatalError("NSMeasurement cannot be encoded by non-keyed archivers") + } + + aCoder.encode(doubleValue, forKey: "NS.value") + aCoder.encode(unit, forKey: "NS.unit") + } + + open override func isEqual(_ object: Any?) -> Bool { + guard let measurement = object as? NSMeasurement else { return false } + return measurement.unit.isEqual(self.unit) && doubleValue == measurement.doubleValue + } + + open override var hash: Int { + return Int(doubleValue) ^ unit.hash + } } extension NSMeasurement : _StructTypeBridgeable { diff --git a/Foundation/Unit.swift b/Foundation/Unit.swift index 1baa48c447..f35e36ab34 100644 --- a/Foundation/Unit.swift +++ b/Foundation/Unit.swift @@ -94,25 +94,26 @@ open class UnitConverterLinear : UnitConverter, NSSecureCoding { } } -private class UnitConverterReciprocal : UnitConverter, NSSecureCoding { +// This must be named with a NS prefix because it can be sometimes encoded by Darwin, and we need to match the name in the archive. +internal class NSUnitConverterReciprocal : UnitConverter, NSSecureCoding { private var reciprocal: Double - fileprivate init(reciprocal: Double) { + init(reciprocal: Double) { self.reciprocal = reciprocal } - fileprivate override func baseUnitValue(fromValue value: Double) -> Double { + override func baseUnitValue(fromValue value: Double) -> Double { return reciprocal / value } - fileprivate override func value(fromBaseUnitValue baseUnitValue: Double) -> Double { + override func value(fromBaseUnitValue baseUnitValue: Double) -> Double { return reciprocal / baseUnitValue } - fileprivate required convenience init?(coder aDecoder: NSCoder) { + required convenience init?(coder aDecoder: NSCoder) { guard aDecoder.allowsKeyedCoding else { preconditionFailure("Unkeyed coding is unsupported.") } @@ -120,17 +121,17 @@ private class UnitConverterReciprocal : UnitConverter, NSSecureCoding { self.init(reciprocal: reciprocal) } - fileprivate func encode(with aCoder: NSCoder) { + func encode(with aCoder: NSCoder) { guard aCoder.allowsKeyedCoding else { preconditionFailure("Unkeyed coding is unsupported.") } aCoder.encode(self.reciprocal, forKey:"NS.reciprocal") } - fileprivate static var supportsSecureCoding: Bool { return true } + static var supportsSecureCoding: Bool { return true } open override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? UnitConverterReciprocal else { + guard let other = object as? NSUnitConverterReciprocal else { return false } @@ -215,8 +216,8 @@ open class Dimension : Unit { preconditionFailure("Unkeyed coding is unsupported.") } guard - let symbol = aDecoder.decodeObject(forKey: "NS.symbol") as? String, - let converter = aDecoder.decodeObject(forKey: "NS.converter") as? UnitConverter + let symbol = aDecoder.decodeObject(of: NSString.self, forKey: "NS.symbol")?._swiftObject, + let converter = aDecoder.decodeObject(of: [UnitConverterLinear.self, NSUnitConverterReciprocal.self], forKey: "NS.converter") as? UnitConverter else { return nil } self.converter = converter super.init(symbol: symbol) @@ -1168,7 +1169,7 @@ public final class UnitFuelEfficiency : Dimension { } private convenience init(symbol: String, reciprocal: Double) { - self.init(symbol: symbol, converter: UnitConverterReciprocal(reciprocal: reciprocal)) + self.init(symbol: symbol, converter: NSUnitConverterReciprocal(reciprocal: reciprocal)) } public class var litersPer100Kilometers: UnitFuelEfficiency { diff --git a/TestFoundation/FixtureValues.swift b/TestFoundation/FixtureValues.swift index 53642b6c61..ebcdc88b1c 100644 --- a/TestFoundation/FixtureValues.swift +++ b/TestFoundation/FixtureValues.swift @@ -301,6 +301,25 @@ enum Fixtures { return NSMutableOrderedSet() } + // ===== NSMeasurement ===== + + static let zeroMeasurement = TypedFixture("NSMeasurement-Zero") { + let noUnit = Unit(symbol: "") + return NSMeasurement(doubleValue: 0, unit: noUnit) + } + + static let lengthMeasurement = TypedFixture("NSMeasurement-Length") { + return NSMeasurement(doubleValue: 45, unit: UnitLength.miles) + } + + static let frequencyMeasurement = TypedFixture("NSMeasurement-Frequency") { + return NSMeasurement(doubleValue: 1400, unit: UnitFrequency.megahertz) + } + + static let angleMeasurement = TypedFixture("NSMeasurement-Angle") { + return NSMeasurement(doubleValue: 90, unit: UnitAngle.degrees) + } + // ===== Fixture list ===== static let _listOfAllFixtures: [AnyFixture] = [ @@ -341,6 +360,10 @@ enum Fixtures { AnyFixture(Fixtures.orderedSetEmpty), AnyFixture(Fixtures.mutableOrderedSetOfNumbers), AnyFixture(Fixtures.mutableOrderedSetEmpty), + AnyFixture(Fixtures.zeroMeasurement), + AnyFixture(Fixtures.lengthMeasurement), + AnyFixture(Fixtures.frequencyMeasurement), + AnyFixture(Fixtures.angleMeasurement), ] // This ensures that we do not have fixtures with duplicate identifiers: diff --git a/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Angle.archive b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Angle.archive new file mode 100644 index 0000000000..e9f4f3c6cf Binary files /dev/null and b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Angle.archive differ diff --git a/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Frequency.archive b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Frequency.archive new file mode 100644 index 0000000000..579c0c839e Binary files /dev/null and b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Frequency.archive differ diff --git a/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Length.archive b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Length.archive new file mode 100644 index 0000000000..5c0fb5c047 Binary files /dev/null and b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Length.archive differ diff --git a/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Zero.archive b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Zero.archive new file mode 100644 index 0000000000..d4a44980c4 Binary files /dev/null and b/TestFoundation/Fixtures/macOS-10.14/NSMeasurement-Zero.archive differ diff --git a/TestFoundation/TestMeasurement.swift b/TestFoundation/TestMeasurement.swift index 77eb701237..97143b4014 100644 --- a/TestFoundation/TestMeasurement.swift +++ b/TestFoundation/TestMeasurement.swift @@ -23,12 +23,6 @@ class CustomUnit: Unit { #endif class TestMeasurement: XCTestCase { - static var allTests: [(String, (TestMeasurement) -> () throws -> Void)] { - return [ - ("testHashing", testHashing), - ] - } - func testHashing() { let lengths: [[Measurement]] = [ [ @@ -75,5 +69,29 @@ class TestMeasurement: XCTestCase { checkHashable(custom, equalityOracle: { $0 == $1 }) #endif } + + let fixtures = [ + Fixtures.zeroMeasurement, + Fixtures.lengthMeasurement, + Fixtures.frequencyMeasurement, + Fixtures.angleMeasurement, + ] + func testCodingRoundtrip() throws { + for fixture in fixtures { + try fixture.assertValueRoundtripsInCoder() + } + } + + func testLoadedValuesMatch() throws { + for fixture in fixtures { + try fixture.assertLoadedValuesMatch() + } + } + + static let allTests = [ + ("testHashing", testHashing), + ("testCodingRoundtrip", testCodingRoundtrip), + ("testLoadedValuesMatch", testLoadedValuesMatch), + ] } diff --git a/TestFoundation/TestUnitConverter.swift b/TestFoundation/TestUnitConverter.swift index 01545c5cdf..f5a3990a38 100644 --- a/TestFoundation/TestUnitConverter.swift +++ b/TestFoundation/TestUnitConverter.swift @@ -273,7 +273,7 @@ class TestUnitConverter: XCTestCase { XCTAssertNotEqual(u1, u4) XCTAssertNotEqual(u4, u1) - // Cannot test UnitConverterReciprocal due to no support for @testable import. + // Cannot test NSUnitConverterReciprocal due to no support for @testable import. } }