NSCoding和可编码属性<=> JSON格式<=>(读/写)文件

时间:2018-08-18 18:32:02

标签: json swift serialization nscoding codable

我需要从JSON格式的文件中读取/写入可编码(例如Date)和NSCoding(例如NSMutableAttributedString)的属性。在研究了如何使用Codable读取和写入文件,如何以JSON格式进行读写以及在某些属性不符合Codable(但符合NSCoding)时如何将NSCoding与Codable结合之后,我共同思考了一下以下代码,让我感到困惑。

我终于想出了如何进行测试,并进行了相应的更改。但是我仍然想知道三种解码器/编码器类型(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 []
        }
    }
}

1 个答案:

答案 0 :(得分:1)

  

关于您的替代方案,我不知道该怎么做。

我尝试为Codable实现NSMutableAttributedString。我不得不嵌入而不是对其进行子类化,因为它是一个类集群。 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)
}
  

但是它看起来也不对。例如,显式定义的解码器和编码器似乎与JSONDecoder和-Encoder断开连接。

Codable结构彼此建立。如果所有基础结构都实现Codable,则编译器可以自行创建编码和解码功能。如果没有,开发人员必须对其进行编码,然后将其放在CodingKey上,以便进行解码。

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

  

应该有可辨别的处理流,但是我看不到三种解码器/编码器如何交互或相互替代。

有支持特定编码器/解码器对的解码器/编码器和方法。

NSCodingNSKeyedUnarchiver/NSKeyedArchiver一起使用,并返回NSData,它只是数据,尽管它不是人类可读的形式。

Codable与支持Codable的任何编码器/解码器对一起使用,更具体地说,在我们的案例JSONEncoder/JSONDecoder中,它返回人类可读格式的Data JSON并可以打印,因为此处的数据是用.utf8编码的。