Skip to content

Parity: NSCoding: ByteCountFormatter, failWithError(_:) & ObjC API Notes compatibility. #2094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion Foundation/ByteCountFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,64 @@ open class ByteCountFormatter : Formatter {
}

public required init?(coder: NSCoder) {
NSUnimplemented()
super.init(coder: coder)
precondition(coder.allowsKeyedCoding)

if let context = Formatter.Context(rawValue: Int(coder.decodeInt32(forKey: "NSFormattingContext"))) {
self.formattingContext = context
}

self.allowedUnits = Units(rawValue: UInt(coder.decodeInt32(forKey: "NSUnits")))

if let countStyle = CountStyle(rawValue: Int(coder.decodeInt32(forKey: "NSKBSize"))) {
self.countStyle = countStyle
}

self.zeroPadsFractionDigits = coder.decodeBool(forKey: "NSZeroPad")
self.includesActualByteCount = coder.decodeBool(forKey: "NSActual")

// These are written in the reverse sense
self.allowsNonnumericFormatting = !coder.decodeBool(forKey: "NSNoNonnumeric")
self.includesUnit = !coder.decodeBool(forKey: "NSNoUnit")
self.includesCount = !coder.decodeBool(forKey: "NSNoCount")
self.isAdaptive = !coder.decodeBool(forKey: "NSNoAdaptive")
}

open override func encode(with coder: NSCoder) {
super.encode(with: coder)
precondition(coder.allowsKeyedCoding)

// The following have 0 as a sentinel raw value, so check
if self.allowedUnits.rawValue != 0 {
coder.encode(Int32(self.allowedUnits.rawValue), forKey: "NSUnits")
}
if self.countStyle.rawValue != 0 {
coder.encode(Int32(self.countStyle.rawValue), forKey: "NSKBSize")
}
if self.formattingContext.rawValue != 0 {
coder.encode(Int32(self.formattingContext.rawValue), forKey: "NSFormattingContext")
}

if self.zeroPadsFractionDigits {
coder.encode(true, forKey: "NSZeroPad")
}
if self.includesActualByteCount {
coder.encode(true, forKey: "NSActual")
}

// The following have true as their default values, so check and write the reverse sense
if !self.allowsNonnumericFormatting {
coder.encode(true, forKey: "NSNoNonnumeric")
}
if !self.includesUnit {
coder.encode(true, forKey: "NSNoUnit")
}
if !self.includesCount {
coder.encode(true, forKey: "NSNoCount")
}
if !self.isAdaptive {
coder.encode(true, forKey: "NSNoAdaptive")
}
}

/* Specify the units that can be used in the output. If ByteCountFormatter.Units is empty, uses platform-appropriate settings; otherwise will only use the specified units. This is the default value. Note that ZB and YB cannot be covered by the range of possible values, but you can still choose to use these units to get fractional display ("0.0035 ZB" for instance).
Expand Down
12 changes: 5 additions & 7 deletions Foundation/NSCoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -699,13 +699,11 @@ open class NSCoder : NSObject {
}

open func failWithError(_ error: Error) {
NSUnimplemented()
// NOTE: disabled for now due to bridging uncertainty
// if let debugDescription = error.userInfo["NSDebugDescription"] {
// NSLog("*** NSKeyedUnarchiver.init: \(debugDescription)")
// } else {
// NSLog("*** NSKeyedUnarchiver.init: decoding error")
// }
if let debugDescription = (error as? NSError)?.userInfo["NSDebugDescription"] {
NSLog("*** NSKeyedUnarchiver.init: \(debugDescription)")
} else {
NSLog("*** NSKeyedUnarchiver.init: decoding error")
}
}

open var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {
Expand Down
123 changes: 122 additions & 1 deletion Foundation/NSObjCRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,118 @@ internal struct _CFInfo {
}
}

// MARK: Classes to strings

// These must remain in sync with Foundation.apinotes as shipped on Apple OSes.
// NSStringFromClass(_:) will return the ObjC name when passed one of these classes, and NSClassFromString(_:) will return the class when passed the ObjC name.
// This is important for NSCoding archives created on Apple OSes to decode with swift-corelibs-foundation and for general source and data format compatibility.

internal let _NSClassesRenamedByObjCAPINotes: [(class: AnyClass, objCName: String)] = [
(CachedURLResponse.self, "NSCachedURLResponse"),
(HTTPCookie.self, "NSHTTPCookie"),
(HTTPCookieStorage.self, "NSHTTPCookieStorage"),
(HTTPURLResponse.self, "NSHTTPURLResponse"),
(ProcessInfo.self, "NSProcessInfo"),
(URLResponse.self, "NSURLResponse"),
(URLSession.self, "NSURLSession"),
(URLSessionConfiguration.self, "NSURLSessionConfiguration"),
(URLSessionDataTask.self, "NSURLSessionDataTask"),
(URLSessionDownloadTask.self, "NSURLSessionDownloadTask"),
(URLSessionStreamTask.self, "NSURLSessionStreamTask"),
(URLSessionTask.self, "NSURLSessionTask"),
(URLSessionUploadTask.self, "NSURLSessionUploadTask"),
(MessagePort.self, "NSMessagePort"),
(Port.self, "NSPort"),
(PortMessage.self, "NSPortMessage"),
(SocketPort.self, "NSSocketPort"),
(Process.self, "NSTask"),
(XMLDTD.self, "NSXMLDTD"),
(XMLDTDNode.self, "NSXMLDTDNode"),
(XMLDocument.self, "NSXMLDocument"),
(XMLElement.self, "NSXMLElement"),
(XMLNode.self, "NSXMLNode"),
(XMLParser.self, "NSXMLParser"),
(Bundle.self, "NSBundle"),
(ByteCountFormatter.self, "NSByteCountFormatter"),
(Host.self, "NSHost"),
(DateComponentsFormatter.self, "NSDateComponentsFormatter"),
(DateFormatter.self, "NSDateFormatter"),
(DateIntervalFormatter.self, "NSDateIntervalFormatter"),
(EnergyFormatter.self, "NSEnergyFormatter"),
(FileHandle.self, "NSFileHandle"),
(FileManager.self, "NSFileManager"),
(Formatter.self, "NSFormatter"),
(InputStream.self, "NSInputStream"),
(ISO8601DateFormatter.self, "NSISO8601DateFormatter"),
(JSONSerialization.self, "NSJSONSerialization"),
(LengthFormatter.self, "NSLengthFormatter"),
(MassFormatter.self, "NSMassFormatter"),
(MeasurementFormatter.self, "NSMeasurementFormatter"),
(NotificationQueue.self, "NSNotificationQueue"),
(NumberFormatter.self, "NSNumberFormatter"),
(Operation.self, "NSOperation"),
(OperationQueue.self, "NSOperationQueue"),
(OutputStream.self, "NSOutputStream"),
(PersonNameComponentsFormatter.self, "NSPersonNameComponentsFormatter"),
(Pipe.self, "NSPipe"),
(Progress.self, "NSProgress"),
(PropertyListSerialization.self, "NSPropertyListSerialization"),
(RunLoop.self, "NSRunLoop"),
(Scanner.self, "NSScanner"),
(Stream.self, "NSStream"),
(Thread.self, "NSThread"),
(Timer.self, "NSTimer"),
(URLAuthenticationChallenge.self, "NSURLAuthenticationChallenge"),
(URLCache.self, "NSURLCache"),
(URLCredential.self, "NSURLCredential"),
(URLCredentialStorage.self, "NSURLCredentialStorage"),
(URLProtectionSpace.self, "NSURLProtectionSpace"),
(URLProtocol.self, "NSURLProtocol"),
(UserDefaults.self, "NSUserDefaults"),
(FileManager.DirectoryEnumerator.self, "NSDirectoryEnumerator"),
(Dimension.self, "NSDimension"),
(Unit.self, "NSUnit"),
(UnitAcceleration.self, "NSUnitAcceleration"),
(UnitAngle.self, "NSUnitAngle"),
(UnitArea.self, "NSUnitArea"),
(UnitConcentrationMass.self, "UnitConcentrationMass"),
(UnitConverter.self, "NSUnitConverter"),
(UnitConverterLinear.self, "NSUnitConverterLinear"),
(UnitDispersion.self, "NSUnitDispersion"),
(UnitDuration.self, "NSUnitDuration"),
(UnitElectricCharge.self, "NSUnitElectricCharge"),
(UnitElectricCurrent.self, "NSUnitElectricCurrent"),
(UnitElectricPotentialDifference.self, "NSUnitElectricPotentialDifference"),
(UnitElectricResistance.self, "NSUnitElectricResistance"),
(UnitEnergy.self, "NSUnitEnergy"),
(UnitFrequency.self, "NSUnitFrequency"),
(UnitFuelEfficiency.self, "NSUnitFuelEfficiency"),
(UnitIlluminance.self, "NSUnitIlluminance"),
(UnitLength.self, "NSUnitLength"),
(UnitMass.self, "NSUnitMass"),
(UnitPower.self, "NSUnitPower"),
(UnitPressure.self, "NSUnitPressure"),
(UnitSpeed.self, "NSUnitSpeed"),
(UnitVolume.self, "NSUnitVolume"),
(UnitTemperature.self, "NSUnitTemperature"),
]

fileprivate var mapFromObjCNameToClass: [String: AnyClass] = {
var map: [String: AnyClass] = [:]
for entry in _NSClassesRenamedByObjCAPINotes {
map[entry.objCName] = entry.class
}
return map
}()

fileprivate var mapFromSwiftClassNameToObjCName: [String: String] = {
var map: [String: String] = [:]
for entry in _NSClassesRenamedByObjCAPINotes {
map[String(reflecting: entry.class)] = entry.objCName
}
return map
}()

#if os(macOS) || os(iOS)
private let _SwiftFoundationModuleName = "SwiftFoundation"
#else
Expand All @@ -243,7 +355,12 @@ private let _SwiftFoundationModuleName = "Foundation"
neither stable nor human-readable.
*/
public func NSStringFromClass(_ aClass: AnyClass) -> String {
let aClassName = String(reflecting: aClass)._bridgeToObjectiveC()
let classNameString = String(reflecting: aClass)
if let renamed = mapFromSwiftClassNameToObjCName[classNameString] {
return renamed
}

let aClassName = classNameString._bridgeToObjectiveC()
let components = aClassName.components(separatedBy: ".")

guard components.count == 2 else {
Expand All @@ -266,6 +383,10 @@ public func NSStringFromClass(_ aClass: AnyClass) -> String {
neither stable nor human-readable.
*/
public func NSClassFromString(_ aClassName: String) -> AnyClass? {
if let renamedClass = mapFromObjCNameToClass[aClassName] {
return renamedClass
}

let aClassNameWithPrefix : String
let components = aClassName._bridgeToObjectiveC().components(separatedBy: ".")

Expand Down
44 changes: 42 additions & 2 deletions TestFoundation/FixtureValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,37 @@ enum Fixtures {
return NSAttributedString(attributedString: try Fixtures.mutableAttributedString.make())
}

// ===== ByteCountFormatter =====

static let byteCountFormatterDefault = TypedFixture<ByteCountFormatter>("ByteCountFormatter-Default") {
return ByteCountFormatter()
}

static let byteCountFormatterAllFieldsSet = TypedFixture<ByteCountFormatter>("ByteCountFormatter-AllFieldsSet") {
let f = ByteCountFormatter()

f.allowedUnits = [.useBytes, .useKB]
f.countStyle = .decimal
f.formattingContext = .beginningOfSentence

f.zeroPadsFractionDigits = true
f.includesCount = true

f.allowsNonnumericFormatting = false
f.includesUnit = false
f.includesCount = false
f.isAdaptive = false

return f
}

// ===== Fixture list =====

static let all: [AnyFixture] = [
AnyFixture(Fixtures.mutableAttributedString),
AnyFixture(Fixtures.attributedString),
AnyFixture(Fixtures.byteCountFormatterDefault),
AnyFixture(Fixtures.byteCountFormatterAllFieldsSet),
]
}

Expand All @@ -68,6 +96,7 @@ protocol Fixture {
associatedtype ValueType
var identifier: String { get }
func make() throws -> ValueType
var supportsSecureCoding: Bool { get }
}

struct TypedFixture<ValueType: NSObject & NSCoding>: Fixture {
Expand All @@ -82,15 +111,21 @@ struct TypedFixture<ValueType: NSObject & NSCoding>: Fixture {
func make() throws -> ValueType {
return try creationHandler()
}

var supportsSecureCoding: Bool {
return (ValueType.self as? NSSecureCoding.Type)?.supportsSecureCoding == true
}
}

struct AnyFixture: Fixture {
var identifier: String
private var creationHandler: () throws -> NSObject & NSCoding
let supportsSecureCoding: Bool

init<T: Fixture>(_ fixture: T) {
self.identifier = fixture.identifier
self.creationHandler = { return try fixture.make() as! (NSObject & NSCoding) }
self.supportsSecureCoding = fixture.supportsSecureCoding
}

func make() throws -> NSObject & NSCoding {
Expand All @@ -106,8 +141,13 @@ extension Fixture where ValueType: NSObject & NSCoding {
func load(fixtureRepository: URL, variant: FixtureVariant) throws -> ValueType? {
let data = try Data(contentsOf: url(inFixtureRepository: fixtureRepository, variant: variant))
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
unarchiver.requiresSecureCoding = true
return try unarchiver.decodeTopLevelObject(of: ValueType.self, forKey: NSKeyedArchiveRootObjectKey)
unarchiver.requiresSecureCoding = self.supportsSecureCoding

let value = unarchiver.decodeObject(of: ValueType.self, forKey: NSKeyedArchiveRootObjectKey)
if let error = unarchiver.error {
throw error
}
return value
}

func url(inFixtureRepository fixtureRepository: URL, variant: FixtureVariant) -> URL {
Expand Down
Binary file not shown.
Binary file not shown.
70 changes: 44 additions & 26 deletions TestFoundation/TestByteCountFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,6 @@

class TestByteCountFormatter : XCTestCase {

static var allTests: [(String, (TestByteCountFormatter) -> () throws -> Void)] {
return [
("test_DefaultValues", test_DefaultValues),
("test_zeroBytes", test_zeroBytes),
("test_oneByte", test_oneByte),
("test_allowedUnitsKBGB", test_allowedUnitsKBGB),
("test_allowedUnitsMBGB", test_allowedUnitsMBGB),
("test_adaptiveFalseAllowedUnitsKBMBGB", test_adaptiveFalseAllowedUnitsKBMBGB),
("test_allowedUnitsKBMBGB", test_allowedUnitsKBMBGB),
("test_allowedUnitsBytesGB", test_allowedUnitsBytesGB),
("test_allowedUnitsGB", test_allowedUnitsGB),
("test_adaptiveFalseAllowedUnitsGB", test_adaptiveFalseAllowedUnitsGB),
("test_numberOnly", test_numberOnly),
("test_unitOnly", test_unitOnly),
("test_isAdaptiveFalse", test_isAdaptiveFalse),
("test_isAdaptiveTrue", test_isAdaptiveTrue),
("test_zeroPadsFractionDigitsTrue", test_zeroPadsFractionDigitsTrue),
("test_isAdaptiveFalseZeroPadsFractionDigitsTrue", test_isAdaptiveFalseZeroPadsFractionDigitsTrue),
("test_countStyleDecimal", test_countStyleDecimal),
("test_countStyleBinary", test_countStyleBinary),
("test_largeByteValues", test_largeByteValues),
("test_negativeByteValues", test_negativeByteValues)

]
}

func test_DefaultValues() {
let formatter = ByteCountFormatter()
XCTAssertEqual(formatter.allowedUnits, [])
Expand Down Expand Up @@ -464,4 +438,48 @@ class TestByteCountFormatter : XCTestCase {
formatter.allowedUnits = .useYBOrHigher
XCTAssertEqual(formatter.string(fromByteCount: Int64.min), "-0 YB")
}

func test_unarchivingFixtures() throws {
for fixture in [Fixtures.byteCountFormatterDefault, Fixtures.byteCountFormatterAllFieldsSet] {
let expectation = try fixture.make()
try fixture.loadEach { (formatter, variant) in
XCTAssertEqual(formatter.allowedUnits, expectation.allowedUnits)
XCTAssertEqual(formatter.countStyle, expectation.countStyle)
XCTAssertEqual(formatter.formattingContext, expectation.formattingContext)
XCTAssertEqual(formatter.zeroPadsFractionDigits, expectation.zeroPadsFractionDigits)
XCTAssertEqual(formatter.includesActualByteCount, expectation.includesActualByteCount)
XCTAssertEqual(formatter.allowsNonnumericFormatting, expectation.allowsNonnumericFormatting)
XCTAssertEqual(formatter.includesUnit, expectation.includesUnit)
XCTAssertEqual(formatter.includesCount, expectation.includesCount)
XCTAssertEqual(formatter.isAdaptive, expectation.isAdaptive)
}
}
}

static var allTests: [(String, (TestByteCountFormatter) -> () throws -> Void)] {
return [
("test_DefaultValues", test_DefaultValues),
("test_zeroBytes", test_zeroBytes),
("test_oneByte", test_oneByte),
("test_allowedUnitsKBGB", test_allowedUnitsKBGB),
("test_allowedUnitsMBGB", test_allowedUnitsMBGB),
("test_adaptiveFalseAllowedUnitsKBMBGB", test_adaptiveFalseAllowedUnitsKBMBGB),
("test_allowedUnitsKBMBGB", test_allowedUnitsKBMBGB),
("test_allowedUnitsBytesGB", test_allowedUnitsBytesGB),
("test_allowedUnitsGB", test_allowedUnitsGB),
("test_adaptiveFalseAllowedUnitsGB", test_adaptiveFalseAllowedUnitsGB),
("test_numberOnly", test_numberOnly),
("test_unitOnly", test_unitOnly),
("test_isAdaptiveFalse", test_isAdaptiveFalse),
("test_isAdaptiveTrue", test_isAdaptiveTrue),
("test_zeroPadsFractionDigitsTrue", test_zeroPadsFractionDigitsTrue),
("test_isAdaptiveFalseZeroPadsFractionDigitsTrue", test_isAdaptiveFalseZeroPadsFractionDigitsTrue),
("test_countStyleDecimal", test_countStyleDecimal),
("test_countStyleBinary", test_countStyleBinary),
("test_largeByteValues", test_largeByteValues),
("test_negativeByteValues", test_negativeByteValues),
("test_unarchivingFixtures", test_unarchivingFixtures),
]
}

}
Loading