Skip to content

[SR-2416] allow non-NSObject types to be archived #574

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
Oct 27, 2016
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
33 changes: 18 additions & 15 deletions Foundation/NSKeyedArchiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,17 @@ open class NSKeyedArchiver : NSCoder {
return NSKeyedArchiveNullObjectReference
}

uid = self._objRefMap[objv as! AnyHashable]
let value = _SwiftValue.store(objv)!

uid = self._objRefMap[value]
if uid == nil {
if conditional {
return nil // object has not been unconditionally encoded
}

uid = UInt32(self._objects.count)

self._objRefMap[objv as! AnyHashable] = uid
self._objRefMap[value] = uid
self._objects.insert(NSKeyedArchiveNullObjectReferenceName, at: Int(uid!))
}

Expand All @@ -293,7 +295,7 @@ open class NSKeyedArchiver : NSCoder {
if objv == nil {
return true // always have a null reference
} else {
return self._objRefMap[objv as! AnyHashable] != nil
return self._objRefMap[_SwiftValue.store(objv)] != nil
}
}

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

self._replacementMap[object as! AnyHashable] = replacement
self._replacementMap[_SwiftValue.store(object)] = replacement
}

/**
Expand Down Expand Up @@ -477,9 +479,8 @@ open class NSKeyedArchiver : NSCoder {

// object replaced by NSObject.replacementObjectForKeyedArchiver
// if it is replaced with nil, it cannot be further replaced
if objectToEncode == nil {
let ns = object as? NSObject
objectToEncode = ns?.replacementObjectForKeyedArchiver(self)
if let ns = objectToEncode as? NSObject {
objectToEncode = ns.replacementObjectForKeyedArchiver(self)
if objectToEncode == nil {
replaceObject(object!, withObject: nil)
return nil
Expand Down Expand Up @@ -511,7 +512,12 @@ open class NSKeyedArchiver : NSCoder {

haveVisited = _haveVisited(objv)
object = _replacementObject(objv)


// bridge value types
if let bridgedObject = object as? _ObjectBridgeable {
object = bridgedObject._bridgeToAnyObject()
}

objectRef = _referenceObject(object, conditional: conditional)
guard let unwrappedObjectRef = objectRef else {
// we can return nil if the object is being conditionally encoded
Expand All @@ -532,12 +538,9 @@ open class NSKeyedArchiver : NSCoder {
_pushEncodingContext(innerEncodingContext)
codable.encode(with: self)

guard let ns = object as? NSObject else {
fatalError("Attempt to encode non-NSObject");
}

let cls : AnyClass = ns.classForKeyedArchiver ?? type(of: object) as! AnyClass

let ns = object as? NSObject
let cls : AnyClass = ns?.classForKeyedArchiver ?? type(of: object!) as! AnyClass

_setObjectInCurrentEncodingContext(_classReference(cls), forKey: "$class", escape: false)
_popEncodingContext()
encodedObject = innerEncodingContext.dict
Expand Down
9 changes: 4 additions & 5 deletions Foundation/NSKeyedUnarchiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ open class NSKeyedUnarchiver : NSCoder {
unwrappedDelegate.unarchiver(self, willReplace: object, with: replacement)
}

self._replacementMap[object as! AnyHashable] = replacement
self._replacementMap[_SwiftValue.store(object)] = replacement
}

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

// check replacement cache
object = self._replacementMap[decodedObject as! AnyHashable]
object = self._replacementMap[_SwiftValue.store(decodedObject)]
if object != nil {
return object
}
Expand Down Expand Up @@ -489,9 +489,8 @@ open class NSKeyedUnarchiver : NSCoder {
}
} else {
// reference to a non-container object
// FIXME remove these special cases
if let str = dereferencedObject as? String {
object = str._bridgeToObjectiveC()
if let bridgedObject = dereferencedObject as? _ObjectBridgeable {
object = bridgedObject._bridgeToAnyObject()
} else {
object = dereferencedObject
}
Expand Down
68 changes: 57 additions & 11 deletions TestFoundation/TestNSKeyedArchiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import SwiftXCTest
#endif

public class UserClass : NSObject, NSSecureCoding {
public class NSUserClass : NSObject, NSSecureCoding {
var ivar : Int

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

public override var description: String {
get {
return "UserClass \(ivar)"
return "NSUserClass \(ivar)"
}
}

public override func isEqual(_ object: Any?) -> Bool {
if let custom = object as? UserClass {
if let custom = object as? NSUserClass {
return self.ivar == custom.ivar
} else {
return false
}
}
}

public class UserClass : CustomStringConvertible, Equatable, Hashable, NSSecureCoding {
var ivar : Int

public class var supportsSecureCoding: Bool {
return true
}

public func encode(with aCoder : NSCoder) {
aCoder.encode(ivar, forKey:"$ivar") // also test escaping
}

init(_ value: Int) {
self.ivar = value
}

public required init?(coder aDecoder: NSCoder) {
self.ivar = aDecoder.decodeInteger(forKey: "$ivar")
}

public var description: String {
get {
return "UserClass \(ivar)"
}
}

public static func ==(lhs: UserClass, rhs: UserClass) -> Bool {
return lhs.ivar == rhs.ivar
}

public var hashValue: Int {
return ivar
}
}

class TestNSKeyedArchiver : XCTestCase {
static var allTests: [(String, (TestNSKeyedArchiver) -> () throws -> Void)] {
return [
Expand All @@ -62,14 +96,16 @@ class TestNSKeyedArchiver : XCTestCase {
("test_archive_string", test_archive_string),
("test_archive_mutable_array", test_archive_mutable_array),
("test_archive_mutable_dictionary", test_archive_mutable_dictionary),
("test_archive_ns_user_class", test_archive_ns_user_class),
("test_archive_nspoint", test_archive_nspoint),
("test_archive_nsrange", test_archive_nsrange),
("test_archive_nsrect", test_archive_nsrect),
("test_archive_null", test_archive_null),
("test_archive_set", test_archive_set),
("test_archive_url", test_archive_url),
("test_archive_user_class", test_archive_user_class),
("test_archive_uuid", test_archive_uuid),
("test_archive_uuid_bvref", test_archive_uuid_byref),
("test_archive_uuid_byvalue", test_archive_uuid_byvalue),
]
}

Expand All @@ -85,7 +121,7 @@ class TestNSKeyedArchiver : XCTestCase {
XCTAssertTrue(decode(unarchiver))
}

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

do {
let rootObj = try unarchiver.decodeTopLevelObject(of: classes, forKey: NSKeyedArchiveRootObjectKey)
guard let root = rootObj as? NSObject else {
guard let rootObj = try unarchiver.decodeTopLevelObject(of: classes, forKey: NSKeyedArchiveRootObjectKey) else {
XCTFail("Unable to decode data")
return false
}
XCTAssertEqual(object, root, "unarchived object \(root) does not match \(object)")

XCTAssertEqual(object as? AnyHashable, rootObj as? AnyHashable, "unarchived object \(rootObj) does not match \(object)")
} catch {
XCTFail("Error thrown: \(error)")
}
return true
})
}

private func test_archive(_ object: NSObject, classes: [AnyClass], allowsSecureCoding: Bool = true) {
private func test_archive(_ object: Any, classes: [AnyClass], allowsSecureCoding: Bool = true) {
// test both XML and binary encodings
test_archive(object, classes: classes, allowsSecureCoding: allowsSecureCoding, outputFormat: PropertyListSerialization.PropertyListFormat.xml)
test_archive(object, classes: classes, allowsSecureCoding: allowsSecureCoding, outputFormat: PropertyListSerialization.PropertyListFormat.binary)
}

private func test_archive(_ object: NSObject, allowsSecureCoding: Bool = true) {
private func test_archive(_ object: AnyObject, allowsSecureCoding: Bool = true) {
return test_archive(object, classes: [type(of: object)], allowsSecureCoding: allowsSecureCoding)
}

Expand Down Expand Up @@ -252,8 +288,18 @@ class TestNSKeyedArchiver : XCTestCase {
test_archive(userClass)
}

func test_archive_uuid() {
func test_archive_ns_user_class() {
let nsUserClass = NSUserClass(5678)
test_archive(nsUserClass)
}

func test_archive_uuid_byref() {
let uuid = NSUUID()
test_archive(uuid)
}

func test_archive_uuid_byvalue() {
let uuid = UUID()
return test_archive(uuid, classes: [NSUUID.self])
}
}