Skip to content

Commit 1d9abe0

Browse files
authored
Merge pull request #2094 from millenomi/bytecountformatter-nscoding
Parity: NSCoding: ByteCountFormatter, failWithError(_:) & ObjC API Notes compatibility.
2 parents 1298c38 + 698709e commit 1d9abe0

File tree

9 files changed

+302
-41
lines changed

9 files changed

+302
-41
lines changed

Foundation/ByteCountFormatter.swift

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,64 @@ open class ByteCountFormatter : Formatter {
4545
}
4646

4747
public required init?(coder: NSCoder) {
48-
NSUnimplemented()
48+
super.init(coder: coder)
49+
precondition(coder.allowsKeyedCoding)
50+
51+
if let context = Formatter.Context(rawValue: Int(coder.decodeInt32(forKey: "NSFormattingContext"))) {
52+
self.formattingContext = context
53+
}
54+
55+
self.allowedUnits = Units(rawValue: UInt(coder.decodeInt32(forKey: "NSUnits")))
56+
57+
if let countStyle = CountStyle(rawValue: Int(coder.decodeInt32(forKey: "NSKBSize"))) {
58+
self.countStyle = countStyle
59+
}
60+
61+
self.zeroPadsFractionDigits = coder.decodeBool(forKey: "NSZeroPad")
62+
self.includesActualByteCount = coder.decodeBool(forKey: "NSActual")
63+
64+
// These are written in the reverse sense
65+
self.allowsNonnumericFormatting = !coder.decodeBool(forKey: "NSNoNonnumeric")
66+
self.includesUnit = !coder.decodeBool(forKey: "NSNoUnit")
67+
self.includesCount = !coder.decodeBool(forKey: "NSNoCount")
68+
self.isAdaptive = !coder.decodeBool(forKey: "NSNoAdaptive")
69+
}
70+
71+
open override func encode(with coder: NSCoder) {
72+
super.encode(with: coder)
73+
precondition(coder.allowsKeyedCoding)
74+
75+
// The following have 0 as a sentinel raw value, so check
76+
if self.allowedUnits.rawValue != 0 {
77+
coder.encode(Int32(self.allowedUnits.rawValue), forKey: "NSUnits")
78+
}
79+
if self.countStyle.rawValue != 0 {
80+
coder.encode(Int32(self.countStyle.rawValue), forKey: "NSKBSize")
81+
}
82+
if self.formattingContext.rawValue != 0 {
83+
coder.encode(Int32(self.formattingContext.rawValue), forKey: "NSFormattingContext")
84+
}
85+
86+
if self.zeroPadsFractionDigits {
87+
coder.encode(true, forKey: "NSZeroPad")
88+
}
89+
if self.includesActualByteCount {
90+
coder.encode(true, forKey: "NSActual")
91+
}
92+
93+
// The following have true as their default values, so check and write the reverse sense
94+
if !self.allowsNonnumericFormatting {
95+
coder.encode(true, forKey: "NSNoNonnumeric")
96+
}
97+
if !self.includesUnit {
98+
coder.encode(true, forKey: "NSNoUnit")
99+
}
100+
if !self.includesCount {
101+
coder.encode(true, forKey: "NSNoCount")
102+
}
103+
if !self.isAdaptive {
104+
coder.encode(true, forKey: "NSNoAdaptive")
105+
}
49106
}
50107

51108
/* 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).

Foundation/NSCoder.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,13 +699,11 @@ open class NSCoder : NSObject {
699699
}
700700

701701
open func failWithError(_ error: Error) {
702-
NSUnimplemented()
703-
// NOTE: disabled for now due to bridging uncertainty
704-
// if let debugDescription = error.userInfo["NSDebugDescription"] {
705-
// NSLog("*** NSKeyedUnarchiver.init: \(debugDescription)")
706-
// } else {
707-
// NSLog("*** NSKeyedUnarchiver.init: decoding error")
708-
// }
702+
if let debugDescription = (error as? NSError)?.userInfo["NSDebugDescription"] {
703+
NSLog("*** NSKeyedUnarchiver.init: \(debugDescription)")
704+
} else {
705+
NSLog("*** NSKeyedUnarchiver.init: decoding error")
706+
}
709707
}
710708

711709
open var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {

Foundation/NSObjCRuntime.swift

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,118 @@ internal struct _CFInfo {
228228
}
229229
}
230230

231+
// MARK: Classes to strings
232+
233+
// These must remain in sync with Foundation.apinotes as shipped on Apple OSes.
234+
// NSStringFromClass(_:) will return the ObjC name when passed one of these classes, and NSClassFromString(_:) will return the class when passed the ObjC name.
235+
// This is important for NSCoding archives created on Apple OSes to decode with swift-corelibs-foundation and for general source and data format compatibility.
236+
237+
internal let _NSClassesRenamedByObjCAPINotes: [(class: AnyClass, objCName: String)] = [
238+
(CachedURLResponse.self, "NSCachedURLResponse"),
239+
(HTTPCookie.self, "NSHTTPCookie"),
240+
(HTTPCookieStorage.self, "NSHTTPCookieStorage"),
241+
(HTTPURLResponse.self, "NSHTTPURLResponse"),
242+
(ProcessInfo.self, "NSProcessInfo"),
243+
(URLResponse.self, "NSURLResponse"),
244+
(URLSession.self, "NSURLSession"),
245+
(URLSessionConfiguration.self, "NSURLSessionConfiguration"),
246+
(URLSessionDataTask.self, "NSURLSessionDataTask"),
247+
(URLSessionDownloadTask.self, "NSURLSessionDownloadTask"),
248+
(URLSessionStreamTask.self, "NSURLSessionStreamTask"),
249+
(URLSessionTask.self, "NSURLSessionTask"),
250+
(URLSessionUploadTask.self, "NSURLSessionUploadTask"),
251+
(MessagePort.self, "NSMessagePort"),
252+
(Port.self, "NSPort"),
253+
(PortMessage.self, "NSPortMessage"),
254+
(SocketPort.self, "NSSocketPort"),
255+
(Process.self, "NSTask"),
256+
(XMLDTD.self, "NSXMLDTD"),
257+
(XMLDTDNode.self, "NSXMLDTDNode"),
258+
(XMLDocument.self, "NSXMLDocument"),
259+
(XMLElement.self, "NSXMLElement"),
260+
(XMLNode.self, "NSXMLNode"),
261+
(XMLParser.self, "NSXMLParser"),
262+
(Bundle.self, "NSBundle"),
263+
(ByteCountFormatter.self, "NSByteCountFormatter"),
264+
(Host.self, "NSHost"),
265+
(DateComponentsFormatter.self, "NSDateComponentsFormatter"),
266+
(DateFormatter.self, "NSDateFormatter"),
267+
(DateIntervalFormatter.self, "NSDateIntervalFormatter"),
268+
(EnergyFormatter.self, "NSEnergyFormatter"),
269+
(FileHandle.self, "NSFileHandle"),
270+
(FileManager.self, "NSFileManager"),
271+
(Formatter.self, "NSFormatter"),
272+
(InputStream.self, "NSInputStream"),
273+
(ISO8601DateFormatter.self, "NSISO8601DateFormatter"),
274+
(JSONSerialization.self, "NSJSONSerialization"),
275+
(LengthFormatter.self, "NSLengthFormatter"),
276+
(MassFormatter.self, "NSMassFormatter"),
277+
(MeasurementFormatter.self, "NSMeasurementFormatter"),
278+
(NotificationQueue.self, "NSNotificationQueue"),
279+
(NumberFormatter.self, "NSNumberFormatter"),
280+
(Operation.self, "NSOperation"),
281+
(OperationQueue.self, "NSOperationQueue"),
282+
(OutputStream.self, "NSOutputStream"),
283+
(PersonNameComponentsFormatter.self, "NSPersonNameComponentsFormatter"),
284+
(Pipe.self, "NSPipe"),
285+
(Progress.self, "NSProgress"),
286+
(PropertyListSerialization.self, "NSPropertyListSerialization"),
287+
(RunLoop.self, "NSRunLoop"),
288+
(Scanner.self, "NSScanner"),
289+
(Stream.self, "NSStream"),
290+
(Thread.self, "NSThread"),
291+
(Timer.self, "NSTimer"),
292+
(URLAuthenticationChallenge.self, "NSURLAuthenticationChallenge"),
293+
(URLCache.self, "NSURLCache"),
294+
(URLCredential.self, "NSURLCredential"),
295+
(URLCredentialStorage.self, "NSURLCredentialStorage"),
296+
(URLProtectionSpace.self, "NSURLProtectionSpace"),
297+
(URLProtocol.self, "NSURLProtocol"),
298+
(UserDefaults.self, "NSUserDefaults"),
299+
(FileManager.DirectoryEnumerator.self, "NSDirectoryEnumerator"),
300+
(Dimension.self, "NSDimension"),
301+
(Unit.self, "NSUnit"),
302+
(UnitAcceleration.self, "NSUnitAcceleration"),
303+
(UnitAngle.self, "NSUnitAngle"),
304+
(UnitArea.self, "NSUnitArea"),
305+
(UnitConcentrationMass.self, "UnitConcentrationMass"),
306+
(UnitConverter.self, "NSUnitConverter"),
307+
(UnitConverterLinear.self, "NSUnitConverterLinear"),
308+
(UnitDispersion.self, "NSUnitDispersion"),
309+
(UnitDuration.self, "NSUnitDuration"),
310+
(UnitElectricCharge.self, "NSUnitElectricCharge"),
311+
(UnitElectricCurrent.self, "NSUnitElectricCurrent"),
312+
(UnitElectricPotentialDifference.self, "NSUnitElectricPotentialDifference"),
313+
(UnitElectricResistance.self, "NSUnitElectricResistance"),
314+
(UnitEnergy.self, "NSUnitEnergy"),
315+
(UnitFrequency.self, "NSUnitFrequency"),
316+
(UnitFuelEfficiency.self, "NSUnitFuelEfficiency"),
317+
(UnitIlluminance.self, "NSUnitIlluminance"),
318+
(UnitLength.self, "NSUnitLength"),
319+
(UnitMass.self, "NSUnitMass"),
320+
(UnitPower.self, "NSUnitPower"),
321+
(UnitPressure.self, "NSUnitPressure"),
322+
(UnitSpeed.self, "NSUnitSpeed"),
323+
(UnitVolume.self, "NSUnitVolume"),
324+
(UnitTemperature.self, "NSUnitTemperature"),
325+
]
326+
327+
fileprivate var mapFromObjCNameToClass: [String: AnyClass] = {
328+
var map: [String: AnyClass] = [:]
329+
for entry in _NSClassesRenamedByObjCAPINotes {
330+
map[entry.objCName] = entry.class
331+
}
332+
return map
333+
}()
334+
335+
fileprivate var mapFromSwiftClassNameToObjCName: [String: String] = {
336+
var map: [String: String] = [:]
337+
for entry in _NSClassesRenamedByObjCAPINotes {
338+
map[String(reflecting: entry.class)] = entry.objCName
339+
}
340+
return map
341+
}()
342+
231343
#if os(macOS) || os(iOS)
232344
private let _SwiftFoundationModuleName = "SwiftFoundation"
233345
#else
@@ -243,7 +355,12 @@ private let _SwiftFoundationModuleName = "Foundation"
243355
neither stable nor human-readable.
244356
*/
245357
public func NSStringFromClass(_ aClass: AnyClass) -> String {
246-
let aClassName = String(reflecting: aClass)._bridgeToObjectiveC()
358+
let classNameString = String(reflecting: aClass)
359+
if let renamed = mapFromSwiftClassNameToObjCName[classNameString] {
360+
return renamed
361+
}
362+
363+
let aClassName = classNameString._bridgeToObjectiveC()
247364
let components = aClassName.components(separatedBy: ".")
248365

249366
guard components.count == 2 else {
@@ -266,6 +383,10 @@ public func NSStringFromClass(_ aClass: AnyClass) -> String {
266383
neither stable nor human-readable.
267384
*/
268385
public func NSClassFromString(_ aClassName: String) -> AnyClass? {
386+
if let renamedClass = mapFromObjCNameToClass[aClassName] {
387+
return renamedClass
388+
}
389+
269390
let aClassNameWithPrefix : String
270391
let components = aClassName._bridgeToObjectiveC().components(separatedBy: ".")
271392

TestFoundation/FixtureValues.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,37 @@ enum Fixtures {
4646
return NSAttributedString(attributedString: try Fixtures.mutableAttributedString.make())
4747
}
4848

49+
// ===== ByteCountFormatter =====
50+
51+
static let byteCountFormatterDefault = TypedFixture<ByteCountFormatter>("ByteCountFormatter-Default") {
52+
return ByteCountFormatter()
53+
}
54+
55+
static let byteCountFormatterAllFieldsSet = TypedFixture<ByteCountFormatter>("ByteCountFormatter-AllFieldsSet") {
56+
let f = ByteCountFormatter()
57+
58+
f.allowedUnits = [.useBytes, .useKB]
59+
f.countStyle = .decimal
60+
f.formattingContext = .beginningOfSentence
61+
62+
f.zeroPadsFractionDigits = true
63+
f.includesCount = true
64+
65+
f.allowsNonnumericFormatting = false
66+
f.includesUnit = false
67+
f.includesCount = false
68+
f.isAdaptive = false
69+
70+
return f
71+
}
72+
73+
// ===== Fixture list =====
74+
4975
static let all: [AnyFixture] = [
5076
AnyFixture(Fixtures.mutableAttributedString),
5177
AnyFixture(Fixtures.attributedString),
78+
AnyFixture(Fixtures.byteCountFormatterDefault),
79+
AnyFixture(Fixtures.byteCountFormatterAllFieldsSet),
5280
]
5381
}
5482

@@ -68,6 +96,7 @@ protocol Fixture {
6896
associatedtype ValueType
6997
var identifier: String { get }
7098
func make() throws -> ValueType
99+
var supportsSecureCoding: Bool { get }
71100
}
72101

73102
struct TypedFixture<ValueType: NSObject & NSCoding>: Fixture {
@@ -82,15 +111,21 @@ struct TypedFixture<ValueType: NSObject & NSCoding>: Fixture {
82111
func make() throws -> ValueType {
83112
return try creationHandler()
84113
}
114+
115+
var supportsSecureCoding: Bool {
116+
return (ValueType.self as? NSSecureCoding.Type)?.supportsSecureCoding == true
117+
}
85118
}
86119

87120
struct AnyFixture: Fixture {
88121
var identifier: String
89122
private var creationHandler: () throws -> NSObject & NSCoding
123+
let supportsSecureCoding: Bool
90124

91125
init<T: Fixture>(_ fixture: T) {
92126
self.identifier = fixture.identifier
93127
self.creationHandler = { return try fixture.make() as! (NSObject & NSCoding) }
128+
self.supportsSecureCoding = fixture.supportsSecureCoding
94129
}
95130

96131
func make() throws -> NSObject & NSCoding {
@@ -106,8 +141,13 @@ extension Fixture where ValueType: NSObject & NSCoding {
106141
func load(fixtureRepository: URL, variant: FixtureVariant) throws -> ValueType? {
107142
let data = try Data(contentsOf: url(inFixtureRepository: fixtureRepository, variant: variant))
108143
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
109-
unarchiver.requiresSecureCoding = true
110-
return try unarchiver.decodeTopLevelObject(of: ValueType.self, forKey: NSKeyedArchiveRootObjectKey)
144+
unarchiver.requiresSecureCoding = self.supportsSecureCoding
145+
146+
let value = unarchiver.decodeObject(of: ValueType.self, forKey: NSKeyedArchiveRootObjectKey)
147+
if let error = unarchiver.error {
148+
throw error
149+
}
150+
return value
111151
}
112152

113153
func url(inFixtureRepository fixtureRepository: URL, variant: FixtureVariant) -> URL {
Binary file not shown.
Binary file not shown.

TestFoundation/TestByteCountFormatter.swift

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,6 @@
99

1010
class TestByteCountFormatter : XCTestCase {
1111

12-
static var allTests: [(String, (TestByteCountFormatter) -> () throws -> Void)] {
13-
return [
14-
("test_DefaultValues", test_DefaultValues),
15-
("test_zeroBytes", test_zeroBytes),
16-
("test_oneByte", test_oneByte),
17-
("test_allowedUnitsKBGB", test_allowedUnitsKBGB),
18-
("test_allowedUnitsMBGB", test_allowedUnitsMBGB),
19-
("test_adaptiveFalseAllowedUnitsKBMBGB", test_adaptiveFalseAllowedUnitsKBMBGB),
20-
("test_allowedUnitsKBMBGB", test_allowedUnitsKBMBGB),
21-
("test_allowedUnitsBytesGB", test_allowedUnitsBytesGB),
22-
("test_allowedUnitsGB", test_allowedUnitsGB),
23-
("test_adaptiveFalseAllowedUnitsGB", test_adaptiveFalseAllowedUnitsGB),
24-
("test_numberOnly", test_numberOnly),
25-
("test_unitOnly", test_unitOnly),
26-
("test_isAdaptiveFalse", test_isAdaptiveFalse),
27-
("test_isAdaptiveTrue", test_isAdaptiveTrue),
28-
("test_zeroPadsFractionDigitsTrue", test_zeroPadsFractionDigitsTrue),
29-
("test_isAdaptiveFalseZeroPadsFractionDigitsTrue", test_isAdaptiveFalseZeroPadsFractionDigitsTrue),
30-
("test_countStyleDecimal", test_countStyleDecimal),
31-
("test_countStyleBinary", test_countStyleBinary),
32-
("test_largeByteValues", test_largeByteValues),
33-
("test_negativeByteValues", test_negativeByteValues)
34-
35-
]
36-
}
37-
3812
func test_DefaultValues() {
3913
let formatter = ByteCountFormatter()
4014
XCTAssertEqual(formatter.allowedUnits, [])
@@ -464,4 +438,48 @@ class TestByteCountFormatter : XCTestCase {
464438
formatter.allowedUnits = .useYBOrHigher
465439
XCTAssertEqual(formatter.string(fromByteCount: Int64.min), "-0 YB")
466440
}
441+
442+
func test_unarchivingFixtures() throws {
443+
for fixture in [Fixtures.byteCountFormatterDefault, Fixtures.byteCountFormatterAllFieldsSet] {
444+
let expectation = try fixture.make()
445+
try fixture.loadEach { (formatter, variant) in
446+
XCTAssertEqual(formatter.allowedUnits, expectation.allowedUnits)
447+
XCTAssertEqual(formatter.countStyle, expectation.countStyle)
448+
XCTAssertEqual(formatter.formattingContext, expectation.formattingContext)
449+
XCTAssertEqual(formatter.zeroPadsFractionDigits, expectation.zeroPadsFractionDigits)
450+
XCTAssertEqual(formatter.includesActualByteCount, expectation.includesActualByteCount)
451+
XCTAssertEqual(formatter.allowsNonnumericFormatting, expectation.allowsNonnumericFormatting)
452+
XCTAssertEqual(formatter.includesUnit, expectation.includesUnit)
453+
XCTAssertEqual(formatter.includesCount, expectation.includesCount)
454+
XCTAssertEqual(formatter.isAdaptive, expectation.isAdaptive)
455+
}
456+
}
457+
}
458+
459+
static var allTests: [(String, (TestByteCountFormatter) -> () throws -> Void)] {
460+
return [
461+
("test_DefaultValues", test_DefaultValues),
462+
("test_zeroBytes", test_zeroBytes),
463+
("test_oneByte", test_oneByte),
464+
("test_allowedUnitsKBGB", test_allowedUnitsKBGB),
465+
("test_allowedUnitsMBGB", test_allowedUnitsMBGB),
466+
("test_adaptiveFalseAllowedUnitsKBMBGB", test_adaptiveFalseAllowedUnitsKBMBGB),
467+
("test_allowedUnitsKBMBGB", test_allowedUnitsKBMBGB),
468+
("test_allowedUnitsBytesGB", test_allowedUnitsBytesGB),
469+
("test_allowedUnitsGB", test_allowedUnitsGB),
470+
("test_adaptiveFalseAllowedUnitsGB", test_adaptiveFalseAllowedUnitsGB),
471+
("test_numberOnly", test_numberOnly),
472+
("test_unitOnly", test_unitOnly),
473+
("test_isAdaptiveFalse", test_isAdaptiveFalse),
474+
("test_isAdaptiveTrue", test_isAdaptiveTrue),
475+
("test_zeroPadsFractionDigitsTrue", test_zeroPadsFractionDigitsTrue),
476+
("test_isAdaptiveFalseZeroPadsFractionDigitsTrue", test_isAdaptiveFalseZeroPadsFractionDigitsTrue),
477+
("test_countStyleDecimal", test_countStyleDecimal),
478+
("test_countStyleBinary", test_countStyleBinary),
479+
("test_largeByteValues", test_largeByteValues),
480+
("test_negativeByteValues", test_negativeByteValues),
481+
("test_unarchivingFixtures", test_unarchivingFixtures),
482+
]
483+
}
484+
467485
}

0 commit comments

Comments
 (0)