现有的答案并没有真正解决互操作的问题,而是展示了如何从NSCoding
to Codable
.
我有一个用例,这不是一个选项,而且我确实需要使用NSCoding
from a Codable
语境。如果您好奇:我需要在我的 Mac 应用程序的 XPC 服务之间发送模型,并且这些模型包含NSImage
s。我本可以制作一堆 DTO 来序列化/反序列化图像,但这会是很多样板。此外,这是属性包装器的完美用例。
这是我想出的属性包装:
@propertyWrapper
struct CodableViaNSCoding<T: NSObject & NSCoding>: Codable {
struct FailedToUnarchive: Error { }
let wrappedValue: T
init(wrappedValue: T) { self.wrappedValue = wrappedValue }
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = Self.wrappedValueSupportsSecureCoding
guard let wrappedValue = T(coder: unarchiver) else {
throw FailedToUnarchive()
}
unarchiver.finishDecoding()
self.init(wrappedValue: wrappedValue)
}
func encode(to encoder: Encoder) throws {
let archiver = NSKeyedArchiver(requiringSecureCoding: Self.wrappedValueSupportsSecureCoding)
wrappedValue.encode(with: archiver)
archiver.finishEncoding()
let data = archiver.encodedData
var container = encoder.singleValueContainer()
try container.encode(data)
}
private static var wrappedValueSupportsSecureCoding: Bool {
(T.self as? NSSecureCoding.Type)?.supportsSecureCoding ?? false
}
}
这是我为其编写的简单测试:
import Quick
import Nimble
import Foundation
@objc(FooTests_SampleNSCodingClass)
private class SampleNSCodingClass: NSObject, NSCoding {
let a, b, c: Int
init(a: Int, b: Int, c: Int) {
self.a = a
self.b = b
self.c = c
}
required convenience init?(coder: NSCoder) {
self.init(
a: coder.decodeInteger(forKey: "a"),
b: coder.decodeInteger(forKey: "b"),
c: coder.decodeInteger(forKey: "c")
)
}
func encode(with coder: NSCoder) {
coder.encode(a, forKey: "a")
coder.encode(b, forKey: "b")
coder.encode(c, forKey: "c")
}
}
@objc(FooTests_SampleNSSecureCodingClass)
private class SampleNSSecureCodingClass: SampleNSCodingClass, NSSecureCoding {
static var supportsSecureCoding: Bool { true }
}
private struct S<T: NSObject & NSCoding>: Codable {
@CodableViaNSCoding
var sampleNSCodingObject: T
}
class CodableViaNSCodingSpec: QuickSpec {
override func spec() {
context("Used with a NSCoding value") {
let input = S(sampleNSCodingObject: SampleNSCodingClass(a: 123, b: 456, c: 789))
it("round-trips correctly") {
let encoded = try JSONEncoder().encode(input)
let result = try JSONDecoder().decode(S<SampleNSCodingClass>.self, from: encoded)
expect(result.sampleNSCodingObject.a) == 123
expect(result.sampleNSCodingObject.b) == 456
expect(result.sampleNSCodingObject.c) == 789
}
}
context("Used with a NSSecureCoding value") {
let input = S(sampleNSCodingObject: SampleNSSecureCodingClass(a: 123, b: 456, c: 789))
it("round-trips correctly") {
let encoded = try JSONEncoder().encode(input)
let result = try JSONDecoder().decode(S<SampleNSSecureCodingClass>.self, from: encoded)
expect(result.sampleNSCodingObject.a) == 123
expect(result.sampleNSCodingObject.b) == 456
expect(result.sampleNSCodingObject.c) == 789
}
}
}
}
一些注意事项:
-
如果您需要走另一条路(嵌入Codable
里面的物体NSCoding
存档),您可以使用添加到的现有方法NSCoder
/NSDecoder
-
这将为每个对象创建一个新的存档。除了在编码/解码期间添加相当多的对象分配之外,它还可能使结果膨胀(在我的测试中,空存档的大小约为 220 字节)。
-
Codable
基本上比NSCoding
. Codable
以只能处理具有值语义的对象的方式实现。因此:
- 具有别名(对同一对象的多个引用)的对象图将导致对象重复
- 带有循环的对象图永远无法解码(会有无限递归)
这意味着你无法真正制作一个Encoder
/Decoder
包装纸NSCoder
/NSCoder
类(如NSKeyedArchiver
/NSKeyedUnarchiver
),无需进行大量簿记来检测这些场景并且fatalError
。 (这也意味着您不能支持存档/取消存档任何常规NSCoding
对象,但仅限那些没有别名或循环的对象)。这就是为什么我选择“制作一个独立的存档并将其编码为Data
“ 方法。