【问题标题】:Codable causes loop可编码导致循环
【发布时间】:2017-10-27 14:41:37
【问题描述】:

我有这些类型的课程:

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 可能会进入循环。

我怎样才能防止这种情况发生?

【问题讨论】:

  • 你有一个循环依赖(父) - 这就是它导致循环的原因。只有手动编码实现才能解决这个问题。

标签: swift codable


【解决方案1】:

我找到了这个解决方案:

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(from:)。

希望这有帮助。

【讨论】:

  • 好主意!您也可以通过 reportLines.forEach() { $0.parent = self } 来缩短它
猜你喜欢
  • 2014-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
相关资源
最近更新 更多