Codable导致循环

时间:2017-10-27 14:41:38

标签: swift codable

我有这些类:

import UIKit

class Report: Codable {
    var name: String = ""
    var budgetLines: [ReportLines] = []

    init() {
        self.name = "My Report"
        self.budgetLines = [ReportLines(name: "Test1", parent: self), ReportLines(name: "Test2", parent: self), ReportLines(name: "Test3", parent: self)]
    }
}

class ReportLines: Codable {
    var name: String
    weak var parent: Report?

    init(name: String, parent: Report) {
        self.name = name
        self.parent = parent
    }
}

let report = Report()
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let archiveURL = documentsDirectory.appendingPathComponent("Report").appendingPathExtension("plist")
let propertyListEncoder = PropertyListEncoder()
let encodedReport = try? propertyListEncoder.encode(report)
try? encodedReport?.write(to: archiveURL, options: .noFileProtection)

重要细节:ReportLines类包含指向拥有Report类的指针。这个想法是人们可以编写类似的东西:

myReportLine.parent!.name

当我尝试将其保存为plist时,编码操作会挂起。我怀疑是这种情况,因为它试图编码一个对象,该对象包含一个包含指向外部对象的指针的对象数组。 Codable可能会进入循环。

我该如何防止这种情况?

1 个答案:

答案 0 :(得分:2)

我找到了这个解决方案:

import UIKit

class Report: Codable {
    var name: String = ""
    var reportLines: [ReportLine] = []

    enum ReportCodingKeys: String, CodingKey {
        case name
        case reportLines
    }

    init() {
        self.name = "My Report"
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: ReportCodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        reportLines = try values.decode([ReportLine].self, forKey: .reportLines)

        for reportLine in reportLines {
            reportLine.parent = self
        }
    }

    func addReportLine(name: String) {
        reportLines.append(ReportLine(name: name, parent: self))
    }
}

class ReportLine: Codable {
    var name: String
    weak var parent: Report?

    enum CodingKeys: String, CodingKey {
        case name
    }

    init(name: String, parent: Report) {
        self.name = name
        self.parent = parent
    }
}

let report = Report()
report.addReportLine(name: "Test1")
report.addReportLine(name: "Test2")
report.addReportLine(name: "Test3")

// Saving
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let archiveURL = documentsDirectory.appendingPathComponent("Report").appendingPathExtension("plist")
let propertyListEncoder = PropertyListEncoder()
var encodedReport = try? propertyListEncoder.encode(report)
try? encodedReport?.write(to: archiveURL, options: .noFileProtection)

// Retrieving
let propertyListDecoder = PropertyListDecoder()
encodedReport = try? Data(contentsOf: archiveURL)
let report2 = try? propertyListDecoder.decode(Report.self, from: encodedReport!)

// Comparing
print("\(report.reportLines[2].name ?? "nil")")
print("report!.reportLines[0].parent: \(String(describing: report.reportLines[2].parent?.name ?? "nil"))")
print("\(report2?.reportLines[2].name ?? "nil")")
print("report2!.reportLines[0].parent: \(String(describing: report2?.reportLines[2].parent?.name ?? "nil"))")

我唯一问自己:是否有更好的方法来调用默认解码器而不是自己提供init(来自:)。

希望这有用。