如何为 [[Int?]] 和 [[String?]] 实现核心数据可转换类型

How to implement Core Data Transformable type for [[Int?]] and [[String?]]

对于我的问题,我已经在 GitHub 准备了 a simple SwiftUI project

后端将以下 JSON 数据发送到我的应用程序,代表一个 15 x 15 字母的游戏:

{
    "gid":266,
    "letters":[
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,"H", null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,"U", null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,"E", null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]
    ],
    "values":[
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null, 4,  null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null, 1,  null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null, 1,  null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]
    ],
    "tiles":[
        {"col": 8, "row": 7, "value": 1, "letter": "E"},
        {"col": 7, "row": 7, "value": 1, "letter": "U"},
        {"col": 6, "row": 7, "value": 4, "letter": "H"}
    ]
}

如您所见,lettersvaluesString?Int? 的二维数组,这就是我在 GameModel.swift 用于 JSON 解析:

struct GameModel: Codable, Identifiable {
    var id: Int32 { gid }
    let gid: Int32
    let letters: [[String?]]
    let values: [[Int32?]]
    let tiles: [TileModel]? // the previous move as an array

    // create a new Core Data entity and copy the properties
    func toEntity(viewContext: NSManagedObjectContext) -> GameEntity {
        let gameEntity = GameEntity(context: viewContext)
        gameEntity.gid = self.gid
        gameEntity.letters = self.letters
        gameEntity.values = self.values
        gameEntity.tiles = self.tiles
        return gameEntity
    }
}

struct TileModel: Codable {
    let col: Int
    let row: Int
    let value: Int
    let letter: String
}

我正在尝试使用 Transformable 核心数据类型来解析它们,因此我将这 3 行添加到 Persistence.swift

let container: NSPersistentContainer

init(inMemory: Bool = false) {
    ValueTransformer.setValueTransformer(ValuesToDataTransformer(), forName: .valuesToDataTransformer)
    ValueTransformer.setValueTransformer(LettersToDataTransformer(), forName: .lettersToDataTransformer)
    ValueTransformer.setValueTransformer(TilesToDataTransformer(), forName: .tilesToDataTransformer)

    container = NSPersistentContainer(name: "TransApp")

我还添加了 3 个文件:

我的问题是我的自定义 ValueTransformer sub 类 无法编译。

错误是:

Static method 'unarchivedObject(ofClass:from:)' requires that '[[Int32?]]' conform to 'NSCoding'

其他 2 人也类似。

这是一种完全不同的方法,可以避免 Transformable。它仅将 TileModel 数组保存为 JSON 字符串,并使用计算属性创建网格并将自定义类型与 JSON.

相互转换

实际上你根本不需要解码网格。

  • 在实体中将 tiles 声明为 String

    @NSManaged public var tiles: String?
    
  • 添加计算的 属性 以将 TileModel 的数组转换为 JSON

    var tileArray: [TileModel]? {
        get {
            guard let data = tiles?.data(using: .utf8) else { return nil }
            return try? JSONDecoder().decode([TileModel].self, from: data)
        }
        set {
            guard let data = try? JSONEncoder().encode(newValue) else { tiles = nil; return }
            tiles = String(data: data, encoding: .utf8)
        }
    }
    
  • 和创建网格的计算属性,没有理由将它们保存在实体中

    var values : [[Int?]] {
          var grid : [[Int?]] = [
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil]]
          tileArray?.forEach{ grid[[=12=].col][[=12=].row] = [=12=].value }
          return grid
      }
    
    var letters : [[String?]] {
          var grid : [[String?]] = [
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
              [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil]]
          tileArray?.forEach{ grid[[=12=].col][[=12=].row] = [=12=].letter }
          return grid
      }