MST
星途 面试题库

面试题:Swift Codable协议在复杂数据结构与多态类型数据解析中的实践

考虑一个具有多态性的数据结构,例如有一个基类`Shape`,并派生出`Circle`和`Rectangle`类。 ```swift class Shape { var type: String init(type: String) { self.type = type } } class Circle: Shape { var radius: Double init(radius: Double) { self.radius = radius super.init(type: "circle") } } class Rectangle: Shape { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height super.init(type: "rectangle") } } ``` 现在有一个数组`[Shape]`,其中包含不同类型的`Shape`实例。请设计一种基于Codable协议的方案,将这个数组编码为JSON,并能正确地从JSON解码回原来的数组,需要妥善处理类型信息以实现多态解析,且考虑性能优化。
21.3万 热度难度
编程语言Swift

知识考点

AI 面试

面试题答案

一键面试
  1. 添加Codable一致性
    • 为了让Shape及其子类符合Codable协议,需要手动实现encode(to:)init(from:)方法,因为自动合成无法处理多态情况。
    • 首先,给Shape类添加一个用于标识类型的常量typeKey,并定义一个用于编码和解码类型信息的通用方法。
class Shape: Codable {
    var type: String
    static let typeKey = "type"
    init(type: String) {
        self.type = type
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey:.type)
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decode(String.self, forKey:.type)
    }
    private enum CodingKeys: String, CodingKey {
        case type
    }
}
  • 然后,为Circle类实现Codable协议:
class Circle: Shape {
    var radius: Double
    init(radius: Double) {
        self.radius = radius
        super.init(type: "circle")
    }
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try super.encode(to: encoder)
        try container.encode(radius, forKey:.radius)
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        radius = try container.decode(Double.self, forKey:.radius)
        try super.init(from: decoder)
    }
    private enum CodingKeys: String, CodingKey {
        case radius
    }
}
  • 接着,为Rectangle类实现Codable协议:
class Rectangle: Shape {
    var width: Double
    var height: Double
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
        super.init(type: "rectangle")
    }
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try super.encode(to: encoder)
        try container.encode(width, forKey:.width)
        try container.encode(height, forKey:.height)
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        width = try container.decode(Double.self, forKey:.width)
        height = try container.decode(Double.self, forKey:.height)
        try super.init(from: decoder)
    }
    private enum CodingKeys: String, CodingKey {
        case width
        case height
    }
}
  1. 自定义JSONDecoder
    • 为了正确解码多态类型,需要创建一个自定义的JSONDecoder,并实现一个init(from:)方法来根据类型信息创建正确的实例。
extension JSONDecoder {
    override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
        guard let type = type as? [Shape].Type else {
            return try super.decode(type, from: data)
        }
        let json = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
        var shapes = [Shape]()
        for item in json {
            let typeString = item[Shape.typeKey] as! String
            switch typeString {
            case "circle":
                let decoder = try JSONDecoder().decode(Circle.self, from: try JSONSerialization.data(withJSONObject: item))
                shapes.append(decoder)
            case "rectangle":
                let decoder = try JSONDecoder().decode(Rectangle.self, from: try JSONSerialization.data(withJSONObject: item))
                shapes.append(decoder)
            default:
                fatalError("Unsupported type: \(typeString)")
            }
        }
        return shapes as! T
    }
}
  1. 编码和解码操作
    • 以下是如何使用上述实现进行编码和解码的示例:
let circle = Circle(radius: 5.0)
let rectangle = Rectangle(width: 10.0, height: 5.0)
let shapes: [Shape] = [circle, rectangle]
let encoder = JSONEncoder()
let data = try encoder.encode(shapes)
let decoder = JSONDecoder()
let decodedShapes = try decoder.decode([Shape].self, from: data)

性能优化

  1. 缓存JSONDecoder
    • 避免每次解码时都创建新的JSONDecoder实例。可以在类级别或全局级别创建一个单例JSONDecoder实例,并重复使用。
  2. 使用JSONSerialization直接操作
    • 在自定义的decode方法中,已经部分使用了JSONSerialization。可以进一步优化,直接操作JSON数据结构而不是多次编码和解码。例如,在解析出类型信息后,直接从JSON数据的字典中提取属性值,而不是先转换为Data再解码。但这样做会使代码更复杂,需要仔细处理数据类型和错误情况。
  3. 减少内存分配
    • 在编码过程中,尽量减少中间数据结构的创建。例如,JSONEncoder在默认情况下会创建一些临时的中间数据结构。可以通过优化编码逻辑,减少不必要的内存分配。在解码时,也可以考虑提前分配足够的内存空间来存储解码后的对象,而不是动态增长数组等数据结构。