NSCoding AND Codable 属性 <=> JSON 格式 <=> (Read/Write) 文件

NSCoding AND Codable Properties <=> JSON Format <=> (Read/Write) File

我需要 read/write Codable(例如 Date)和 NSCoding(例如 NSMutableAttributedString)属性 from/to 一个 JSON 格式的文件。在研究了如何使用 Codable 读取和写入文件后,如何以 JSON 格式执行此操作,以及当某些属性不符合 Codable(但符合 NSCoding)时如何将 NSCoding 与 Codable 结合使用,我把下面的代码拼凑在一起,在这个过程中把自己弄糊涂了。

我终于想出了如何测试它,并进行了相应的更改。但我仍然想知道这三种 decoder/encoder 类型(NSCoding、Codable 和 JSON)如何相互作用或相互替代。

import Foundation

class Content: Codable {

    // Content
    var attrStr = NSMutableAttributedString(string: "")
    var date: Date?

    // Initializer for content
    init(attrStr: NSMutableAttributedString, date: Date) {
        self.attrStr = attrStr
        self.date = date
}

    // Need to explicitly define because NSMutableAttributedString isn't codable
    enum CodingKeys: String, CodingKey {

        case attrStr
        case date
    }

    // Need to explicitly define the decoder. . . .
    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        date = try container.decode(Date.self, forKey: .date)

        let attrStrData = try container.decode(Data.self, forKey: .attrStr)
        attrStr = NSKeyedUnarchiver.unarchiveObject(with: attrStrData) as? NSMutableAttributedString ?? NSMutableAttributedString(string: "Error!")
    }

    // Need to explicitly define the encoder. . . .
    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(date, forKey: .date)

        let attrStrData = NSKeyedArchiver.archivedData(withRootObject: attrStr)
        try container.encode(attrStrData, forKey: .attrStr)
    }

    static func getFileURL() -> URL {

        // Get the directory for the file
        let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        // Get the full path and filename
        return docsDir.appendingPathComponent("contentArray").appendingPathExtension("cntnt")
    }

    static func saveToFile(content: [Content]) {

        // Get the file's URL
        let fileURL = getFileURL()

        do {
            // Encode the data
            let data = try JSONEncoder().encode(content)
            // Write to a/the file
            try data.write(to: fileURL)

        } catch {
            print("Could not encode or save to the file!")
        }
    }

    static func loadFromFile() -> [Content] {

        // Get the file's URL
        let fileURL = getFileURL()

        do {
            // Read from the file
            let data = try Data(contentsOf: fileURL)
            // Decode the data
            return try JSONDecoder().decode([Content].self, from: data)

        } catch {
            print("Could not decode or read from the file!")
            return []
        }
    }
}

About your alternative, I wouldn't know how to do that.

我尝试为 NSMutableAttributedString 实施 Codable。我必须嵌入而不是子 classing 它,因为它是一个 class-cluster。 Source

class MutableAttributedStringContainer: Codable {
    let attributedString: NSMutableAttributedString

    init(attributedString: NSMutableAttributedString) {
        self.attributedString = attributedString
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)

        let archiver = try NSKeyedUnarchiver(forReadingFrom: data)
        attributedString = NSMutableAttributedString(coder: archiver)!
    }

    public func encode(to encoder: Encoder) throws {
        let archiver = NSKeyedArchiver(requiringSecureCoding: true)
        attributedString.encode(with: archiver)

        var container = encoder.singleValueContainer()
        try container.encode(archiver.encodedData)
    }
}

这是一个如何使用它的例子。

func testing() {
    let attributedString = NSMutableAttributedString(string: "Hello world!")
    let attributedStringContainer = MutableAttributedStringContainer(attributedString: attributedString)

    // Necessary because encoding into a singleValueContainer() creates a
    // JSON fragment instead of a JSON dictionary that `JSONEncoder` wants
    // create.
    struct Welcome: Codable {
        var attributedString: MutableAttributedStringContainer
    }
    let welcome = Welcome(attributedString: attributedStringContainer)

    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    let data = try! encoder.encode(welcome)
    print(String(bytes: data, encoding: .utf8) as Any)

    let decoder = JSONDecoder()
    let welcome2 = try! decoder.decode(Welcome.self, from: data)
    print("decoded to string:", welcome2.attributedString.attributedString.string)
}

But it also looks wrong. For example, the explicitly defined decoder and encoder seem disconnected from the JSONDecoder and -Encoder.

Codable 结构相互依存。如果所有底层结构都实现 Codable 编译器可以自己创建编码和解码函数。如果没有,开发人员必须对它们进行编码并将它们放在 CodingKey 上,解码也是如此。

例如,可以以任何方式将它们转换为数据,然后将它们作为数据编码为 CodingKey。也许阅读 Codable 上的 Raywenderlich Tutorial 以更好地理解它。

There should be a discernible processing stream, but I can't see how the three kinds of decoders/encoders interact or substitute for one another.

有decoders/encoders和支持特定encoder/decoder-pair的方法。

NSCodingNSKeyedUnarchiver/NSKeyedArchiver 和 returns NSData 一起工作,这只是数据,但不是人类可读的形式。

Codable 与支持 Codable 的任何 encoder/decoder 对一起工作,更具体地说,在我们的例子中 JSONEncoder/JSONDecoder,returns Data这是人类可读的格式 JSON 并且可以打印,因为这里的数据是用 .utf8.

编码的