init(来自解码器:解码器)在单独的便利 init 中导致 "Cannot infer contextual base" 错误

init(from decoder: Decoder) is causing a "Cannot infer contextual base" error in separate convenience init

我正在尝试子class Codable class 并且在我添加 init(from decoder: Decoder) 函数之前它工作正常。然后,编译器在我的 convenience init 上给了我 2 个错误:

Cannot infer contextual base in reference to member 'geometry'

Extra arguments at positions #2, #3, #4, #5 in call

如果删除解码函数,我不会收到任何错误,并且编码函数会按预期工作。我怎样才能同时拥有便利的初始化和解码器功能?有什么不成文的规定禁止这样做吗?

class GeometryNode: Node {
    var values = GeometryNode.Values(shape: .triangle)
    
    enum CodingKeys: String, CodingKey {
        case values
        
        case id
        case type
        case indexPath
    }
    
    // NOTE: GeometryNode.Values is a codable struct
    // NOTE: GeometryNode.Values.Shape a codable enum
    // NOTE: both are defined elsewhere
    convenience init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
        let icon = shape.icon
        let color = shape.color
        let title = shape.rawValue

        // Error shows for below call
        self.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
    }
    
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.values, forKey: .values)
    }
    
    // Remove this function and error above goes away
    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)
        values = try container.decode(GeometryNode.Values.self, forKey: .values)
    }
    
}
extension GeometryNode {
    struct Values: Codable {
        
        var shape: Shape
        
        enum Shape: String, CaseIterable, Codable {
            case triangle
            case rectangle
            case oval
            
            var defaultColor: UIColor {
                return NodeType.geometry.defaultColor
            }
            
            var fontSize: CGFloat {
                return 24
            }
            
            var icon: UIImage {
                return miscValues.icon
            }

            var color: UIColor {
                return miscValues.color
            }

            private var miscValues: (icon: UIImage, color: UIColor) {
                switch self {
                    case .triangle: return ("".textToImage(fontSize: fontSize)!, color: defaultColor)
                    case .rectangle: return ("◾️".textToImage(fontSize: fontSize)!, color: defaultColor)
                    case .oval: return ("⚫️".textToImage(fontSize: fontSize)!, color: defaultColor)
                }
            }
        }
        
        enum CodingKeys: String, CodingKey {
            case shape
        }
    }
}
public class Node: Codable {
    
    
    var id: String?
    var title: String?
    var type: NodeType = .geometry
    var icon: UIImage?
    var color: UIColor?
    var indexPath: IndexPath
    
    var defaultColorForType: UIColor {
        return type.defaultColor
    }
    var absoluteCoordinates: CGPoint? {
        return CGPoint(x: indexPath.item * kCellWidth, y: indexPath.section * kCellHeight)
    }
    func dictionary() -> [String: Any] {
        let data = (try? JSONEncoder().encode(self)) ?? Data()
        return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:]
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case type
        case indexPath
    }
    
    internal init(id: String? = nil, title: String, type: NodeType = .geometry, icon: UIImage? = nil, color: UIColor? = nil, indexPath: IndexPath) {
        self.id = id
        self.title = title
        self.type = type
        self.icon = icon
        self.color = color
        self.indexPath = indexPath
    }
}

extension IndexPath {
    enum CodingKeys: String, CodingKey {
        case item
        case section
    }
}

这不是“不成文的”。这是关于 Swift 的一个重要事实。一旦添加了显式 non-convenience 初始化器,初始化器的继承就会停止运行。所以你试图调用一个不存在的初始化器。

如果您真的不需要便利初始化器,那么您可以在该初始化器中调用 super.init,如下所示:

init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
    let icon = shape.icon
    let color = shape.color
    let title = shape.rawValue

    super.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
}

如果您仍然需要初始化器是 convenience,您可能需要重写超级的初始化器,因为 Swift 只允许方便的初始化器从同一个 class 调用指定的初始化器。

convenience init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
    let icon = shape.icon
    let color = shape.color
    let title = shape.rawValue

    self.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
}

override init(id: String? = nil, title: String, type: NodeType = .geometry, icon: UIImage? = nil, color: UIColor? = nil, indexPath: IndexPath) {
    super.init(id: id, title: title, type: type, icon: icon, color: color, indexPath: indexPath)
}