Skip to content

Commit ec3f61e

Browse files
committed
[SR-2416] allow non-NSObject types to be archived
This patch fixes a number of issues that were preventing non-NSObject types from being archived, even though the new method signatures imply that they can be (by taking Any). * fix regression introduced aa504dc where non-hashable objects could not be encoded * fix bug where non-NSObjects would be replaced with nil, causing a crash * remove explicit check that the encoded object is a NSObject * possibly should be a separate patch: transparently bridge value types to the corresponding reference type before encoding * tests for all over the above
1 parent e170040 commit ec3f61e

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

Foundation/NSKeyedArchiver.swift

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,17 @@ open class NSKeyedArchiver : NSCoder {
271271
return NSKeyedArchiveNullObjectReference
272272
}
273273

274-
uid = self._objRefMap[objv as! AnyHashable]
274+
let value = _SwiftValue.store(objv)!
275+
276+
uid = self._objRefMap[value]
275277
if uid == nil {
276278
if conditional {
277279
return nil // object has not been unconditionally encoded
278280
}
279281

280282
uid = UInt32(self._objects.count)
281283

282-
self._objRefMap[objv as! AnyHashable] = uid
284+
self._objRefMap[value] = uid
283285
self._objects.insert(NSKeyedArchiveNullObjectReferenceName, at: Int(uid!))
284286
}
285287

@@ -293,7 +295,7 @@ open class NSKeyedArchiver : NSCoder {
293295
if objv == nil {
294296
return true // always have a null reference
295297
} else {
296-
return self._objRefMap[objv as! AnyHashable] != nil
298+
return self._objRefMap[_SwiftValue.store(objv)] != nil
297299
}
298300
}
299301

@@ -362,10 +364,10 @@ open class NSKeyedArchiver : NSCoder {
362364
*/
363365
private func replaceObject(_ object: Any, withObject replacement: Any?) {
364366
if let unwrappedDelegate = self.delegate {
365-
unwrappedDelegate.archiver(self, willReplace: object as! AnyHashable, with: replacement)
367+
unwrappedDelegate.archiver(self, willReplace: object, with: replacement)
366368
}
367369

368-
self._replacementMap[object as! AnyHashable] = replacement
370+
self._replacementMap[_SwiftValue.store(object)] = replacement
369371
}
370372

371373
/**
@@ -477,9 +479,8 @@ open class NSKeyedArchiver : NSCoder {
477479

478480
// object replaced by NSObject.replacementObjectForKeyedArchiver
479481
// if it is replaced with nil, it cannot be further replaced
480-
if objectToEncode == nil {
481-
let ns = object as? NSObject
482-
objectToEncode = ns?.replacementObjectForKeyedArchiver(self)
482+
if let ns = objectToEncode as? NSObject {
483+
objectToEncode = ns.replacementObjectForKeyedArchiver(self)
483484
if objectToEncode == nil {
484485
replaceObject(object!, withObject: nil)
485486
return nil
@@ -511,7 +512,12 @@ open class NSKeyedArchiver : NSCoder {
511512

512513
haveVisited = _haveVisited(objv)
513514
object = _replacementObject(objv)
514-
515+
516+
// bridge value types
517+
if let bridgedObject = object as? _ObjectBridgeable {
518+
object = bridgedObject._bridgeToAnyObject()
519+
}
520+
515521
objectRef = _referenceObject(object, conditional: conditional)
516522
guard let unwrappedObjectRef = objectRef else {
517523
// we can return nil if the object is being conditionally encoded
@@ -532,12 +538,9 @@ open class NSKeyedArchiver : NSCoder {
532538
_pushEncodingContext(innerEncodingContext)
533539
codable.encode(with: self)
534540

535-
guard let ns = object as? NSObject else {
536-
fatalError("Attempt to encode non-NSObject");
537-
}
538-
539-
let cls : AnyClass = ns.classForKeyedArchiver ?? type(of: object) as! AnyClass
540-
541+
let ns = object as? NSObject
542+
let cls : AnyClass = ns?.classForKeyedArchiver ?? type(of: object!) as! AnyClass
543+
541544
_setObjectInCurrentEncodingContext(_classReference(cls), forKey: "$class", escape: false)
542545
_popEncodingContext()
543546
encodedObject = innerEncodingContext.dict

Foundation/NSKeyedUnarchiver.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ open class NSKeyedUnarchiver : NSCoder {
375375
unwrappedDelegate.unarchiver(self, willReplace: object, with: replacement)
376376
}
377377

378-
self._replacementMap[object as! AnyHashable] = replacement
378+
self._replacementMap[_SwiftValue.store(object)] = replacement
379379
}
380380

381381
private func _decodingError(_ code: CocoaError.Code, withDescription description: String) -> NSError {
@@ -392,7 +392,7 @@ open class NSKeyedUnarchiver : NSCoder {
392392
}
393393

394394
// check replacement cache
395-
object = self._replacementMap[decodedObject as! AnyHashable]
395+
object = self._replacementMap[_SwiftValue.store(decodedObject)]
396396
if object != nil {
397397
return object
398398
}
@@ -489,9 +489,8 @@ open class NSKeyedUnarchiver : NSCoder {
489489
}
490490
} else {
491491
// reference to a non-container object
492-
// FIXME remove these special cases
493-
if let str = dereferencedObject as? String {
494-
object = str._bridgeToObjectiveC()
492+
if let bridgedObject = dereferencedObject as? _ObjectBridgeable {
493+
object = bridgedObject._bridgeToAnyObject()
495494
} else {
496495
object = dereferencedObject
497496
}

TestFoundation/TestNSKeyedArchiver.swift

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import SwiftXCTest
1717
#endif
1818

19-
public class UserClass : NSObject, NSSecureCoding {
19+
public class NSUserClass : NSObject, NSSecureCoding {
2020
var ivar : Int
2121

2222
public class var supportsSecureCoding: Bool {
@@ -37,19 +37,53 @@ public class UserClass : NSObject, NSSecureCoding {
3737

3838
public override var description: String {
3939
get {
40-
return "UserClass \(ivar)"
40+
return "NSUserClass \(ivar)"
4141
}
4242
}
4343

4444
public override func isEqual(_ object: Any?) -> Bool {
45-
if let custom = object as? UserClass {
45+
if let custom = object as? NSUserClass {
4646
return self.ivar == custom.ivar
4747
} else {
4848
return false
4949
}
5050
}
5151
}
5252

53+
public class UserClass : CustomStringConvertible, Equatable, Hashable, NSSecureCoding {
54+
var ivar : Int
55+
56+
public class var supportsSecureCoding: Bool {
57+
return true
58+
}
59+
60+
public func encode(with aCoder : NSCoder) {
61+
aCoder.encode(ivar, forKey:"$ivar") // also test escaping
62+
}
63+
64+
init(_ value: Int) {
65+
self.ivar = value
66+
}
67+
68+
public required init?(coder aDecoder: NSCoder) {
69+
self.ivar = aDecoder.decodeInteger(forKey: "$ivar")
70+
}
71+
72+
public var description: String {
73+
get {
74+
return "UserClass \(ivar)"
75+
}
76+
}
77+
78+
public static func ==(lhs: UserClass, rhs: UserClass) -> Bool {
79+
return lhs.ivar == rhs.ivar
80+
}
81+
82+
public var hashValue: Int {
83+
return ivar
84+
}
85+
}
86+
5387
class TestNSKeyedArchiver : XCTestCase {
5488
static var allTests: [(String, (TestNSKeyedArchiver) -> () throws -> Void)] {
5589
return [
@@ -62,14 +96,16 @@ class TestNSKeyedArchiver : XCTestCase {
6296
("test_archive_string", test_archive_string),
6397
("test_archive_mutable_array", test_archive_mutable_array),
6498
("test_archive_mutable_dictionary", test_archive_mutable_dictionary),
99+
("test_archive_ns_user_class", test_archive_ns_user_class),
65100
("test_archive_nspoint", test_archive_nspoint),
66101
("test_archive_nsrange", test_archive_nsrange),
67102
("test_archive_nsrect", test_archive_nsrect),
68103
("test_archive_null", test_archive_null),
69104
("test_archive_set", test_archive_set),
70105
("test_archive_url", test_archive_url),
71106
("test_archive_user_class", test_archive_user_class),
72-
("test_archive_uuid", test_archive_uuid),
107+
("test_archive_uuid_bvref", test_archive_uuid_byref),
108+
("test_archive_uuid_byvalue", test_archive_uuid_byvalue),
73109
]
74110
}
75111

@@ -85,7 +121,7 @@ class TestNSKeyedArchiver : XCTestCase {
85121
XCTAssertTrue(decode(unarchiver))
86122
}
87123

88-
private func test_archive(_ object: NSObject, classes: [AnyClass], allowsSecureCoding: Bool = true, outputFormat: PropertyListSerialization.PropertyListFormat) {
124+
private func test_archive(_ object: Any, classes: [AnyClass], allowsSecureCoding: Bool = true, outputFormat: PropertyListSerialization.PropertyListFormat) {
89125
test_archive({ archiver -> Bool in
90126
archiver.requiresSecureCoding = allowsSecureCoding
91127
archiver.outputFormat = outputFormat
@@ -97,26 +133,26 @@ class TestNSKeyedArchiver : XCTestCase {
97133
unarchiver.requiresSecureCoding = allowsSecureCoding
98134

99135
do {
100-
let rootObj = try unarchiver.decodeTopLevelObject(of: classes, forKey: NSKeyedArchiveRootObjectKey)
101-
guard let root = rootObj as? NSObject else {
136+
guard let rootObj = try unarchiver.decodeTopLevelObject(of: classes, forKey: NSKeyedArchiveRootObjectKey) else {
102137
XCTFail("Unable to decode data")
103138
return false
104139
}
105-
XCTAssertEqual(object, root, "unarchived object \(root) does not match \(object)")
140+
141+
XCTAssertEqual(object as? AnyHashable, rootObj as? AnyHashable, "unarchived object \(rootObj) does not match \(object)")
106142
} catch {
107143
XCTFail("Error thrown: \(error)")
108144
}
109145
return true
110146
})
111147
}
112148

113-
private func test_archive(_ object: NSObject, classes: [AnyClass], allowsSecureCoding: Bool = true) {
149+
private func test_archive(_ object: Any, classes: [AnyClass], allowsSecureCoding: Bool = true) {
114150
// test both XML and binary encodings
115151
test_archive(object, classes: classes, allowsSecureCoding: allowsSecureCoding, outputFormat: PropertyListSerialization.PropertyListFormat.xml)
116152
test_archive(object, classes: classes, allowsSecureCoding: allowsSecureCoding, outputFormat: PropertyListSerialization.PropertyListFormat.binary)
117153
}
118154

119-
private func test_archive(_ object: NSObject, allowsSecureCoding: Bool = true) {
155+
private func test_archive(_ object: AnyObject, allowsSecureCoding: Bool = true) {
120156
return test_archive(object, classes: [type(of: object)], allowsSecureCoding: allowsSecureCoding)
121157
}
122158

@@ -252,8 +288,18 @@ class TestNSKeyedArchiver : XCTestCase {
252288
test_archive(userClass)
253289
}
254290

255-
func test_archive_uuid() {
291+
func test_archive_ns_user_class() {
292+
let nsUserClass = NSUserClass(5678)
293+
test_archive(nsUserClass)
294+
}
295+
296+
func test_archive_uuid_byref() {
256297
let uuid = NSUUID()
257298
test_archive(uuid)
258299
}
300+
301+
func test_archive_uuid_byvalue() {
302+
let uuid = UUID()
303+
return test_archive(uuid, classes: [NSUUID.self])
304+
}
259305
}

0 commit comments

Comments
 (0)