Swift数据解析(第二篇) - Codable 下

这是Swift数据解析方案的系列文章:

Swift数据解析(第一篇) - 技术选型

Swift数据解析(第二篇) - Codable 上

Swift数据解析(第二篇) - Codable 下

Swift数据解析(第三篇) - Codable源码学习

Swift数据解析(第四篇) - SmartCodable 上

Swift数据解析(第四篇) - SmartCodable 下

七. 派生关系

1. super.encode(to: encoder)

来看一个这样的场景,我们有一个Point2D的类,包含x和y两个属性,用来标记二维点的位置。 另有继承于Point2D的Point3D,实现三维点定位。

kotlin 复制代码
 class Point2D: Codable {
    var x = 0.0
    var y = 0.0
 }
 ​
 class Point3D: Point2D {
    var z = 0.0
 }

1. z 去哪里了?

ini 复制代码
 let point = Point3D()
 point.x = 1.0
 point.y = 2.0
 point.z = 3.0
 ​
 guard let value = point.encode() else { return }
 print(value)
 ​
 此时的打印结果是:
 {
  "x" : 1,
  "y" : 2
 }

相信你已经反应过来了:子类没有重写父类的 encode 方法,默认使用的父类的 encode 方法。子类的属性自然没有被 encode。

2. x 和 y 的去哪了?

我们将代码做这样的修改:

swift 复制代码
 class Point2D: Codable {
    var x = 0.0
    var y = 0.0
     
    private enum CodingKeys: CodingKey {
        case x
        case y
    }
     
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.x, forKey: .x)
        try container.encode(self.y, forKey: .y)
    }
 }
 ​
 class Point3D: Point2D {
    var z = 0.0
 ​
    enum CodingKeys: CodingKey {
        case z
    }
     
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.z, forKey: .z)
    }
 }
 ​
 此时的打印结果是:
 {
  "z" : 3
 }

相信你应该非常清楚这个原因:子类重写了父类的 decode 实现,导致父类的 encode 没有执行。

3. x, y 和 z 都在了

swift 复制代码
 class Point3D: Point2D {
    var z = 0.0
 ​
    enum CodingKeys: CodingKey {
        case z
    }
     
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
  
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.z, forKey: .z)
    }
 }
 ​
 此时的打印结果是:
 {
  "x" : 1,
  "y" : 2,
  "z" : 3
 }

在子类的 encode 方法里面调用了父类的 encode 方法,完成了子类和父类的属性编码。

2. super.init(from: decoder)

再将打印的值解码成模型。

swift 复制代码
 class Point2D: Codable {
    var x = 0.0
    var y = 0.0
     
    enum CodingKeys: CodingKey {
        case x
        case y
    }
     
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.x = try container.decode(Double.self, forKey: .x)
        self.y = try container.decode(Double.self, forKey: .y)
    }
 }
 ​
 class Point3D: Point2D {
    var z = 0.0
   
    enum CodingKeys: CodingKey {
        case z
        case point2D
    }
     
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.z = try container.decode(Double.self, forKey: .z)
         
        try super.init(from: decoder)
    }
 }
 ​
 let json = """
 {
  "x" : 1,
  "y" : 2,
  "z" : 3
 }
 """
 ​
 guard let point = json.decode(type: Point3D.self) else { return }
 print(point.x)
 print(point.y)
 print(point.z)
 ​
 此时的打印结果是:
  1.0
  2.0
  3.0

3. superEncoder & superDecoder

上面的案例中,父类和子类共享一个 container,这不利于我们我们区分。使用superEncoder创建新的容器:

swift 复制代码
 class Point3D: Point2D {
    var z = 0.0
 ​
    enum CodingKeys: CodingKey {
        case z
    }
     
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.z, forKey: .z)
         
        let superEncoder = container.superEncoder()
        try super.encode(to: superEncoder)
    }
 }
 ​
 输出信息是: 
 {
  "z" : 3,
  "super" : {
    "x" : 1,
    "y" : 2
  }
 }

同样我们使用superDecoder用来编码:

swift 复制代码
 required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.z = try container.decode(Double.self, forKey: .z)
    try super.init(from: container.superDecoder())
 }
 ​
 输出信息是:
 1.0
 2.0
 3.0
理解输出信息中的 super 的含义

我们来看一个示例:这是一个班级的信息,其包含班级号,班长,学生成员的信息。

swift 复制代码
 struct Student: Encodable {
    var name: String
    enum CodingKeys: CodingKey {
        case name
    }
 }
 ​
 struct Class: Encodable {
    var numer: Int
    var monitor: Student
    var students: [Student]
     
    init(numer: Int, monitor: Student, students: [Student]) {
        self.numer = numer
        self.monitor = monitor
        self.students = students
    }
 }
 ​
 let monitor = Student(name: "小明")
 let student1 = Student(name: "大黄")
 let student2 = Student(name: "小李")
 var classFeed = Class(numer: 10, monitor: monitor, students: [student1, student2])
 ​
 guard let value = classFeed.encode() else { return }
 print(value)
 ​
 // 输出信息是:
 {
  "numer" : 10,
  "monitor" : {
    "name" : "小明"
  },
  "students" : [
    {
      "name" : "大黄"
    },
    {
      "name" : "小李"
    }
  ]
 }

重写Class类的encode方法:

swift 复制代码
 func encode(to encoder: Encoder) throws {
     
 }
 ​
 // 报错信息:顶级类没有编码任何值
 Swift.EncodingError.Context(codingPath: [], debugDescription: "Top-level Class did not encode any values.", underlyingError: nil)

只创建容器:

swift 复制代码
 func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
 }
 ​
 // 输出信息是:
 {
 ​
 }

当前container的superEncoder

swift 复制代码
 enum CodingKeys: CodingKey {
    case numer
    case monitor
    case students
     
    // 新增一个key
    case custom
 }
 ​
 func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
     
    var superEncoder = container.superEncoder()
 }
 ​
 // 输出信息是:
 {
  "super" : {
 ​
  }
 }

键值编码容器 和 无键编码容器的下 的区别

swift 复制代码
 func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
     
    // 在monitor字典容器中
    var monitorContainer = container.nestedContainer(keyedBy: Student.CodingKeys.self, forKey: .monitor)
    // 在monitor容器下,新生成一个编码器
    let monitorSuperEncoder = monitorContainer.superEncoder()
 ​
     
    // 在students数组容器中
    var studentsContainer = container.nestedUnkeyedContainer(forKey: .students)
    for student in students {
        let studentsSuperEncoder = studentsContainer.superEncoder()
    }
 }
 ​
 // 打印信息
 {
  "monitor" : {
    "super" : {
 ​
    }
  },
  "students" : [
    {
 ​
    },
    {
 ​
    }
  ]
 }

相信你已经体味到 super 的含义了。

在当前层级下生效 : 使用superEncoder 或 superDecoder 在 当前层级下 生成一个新的Encoder 或 Decoder。

由调用者 container 决定生成的结构:

  • 如果是 KeyedEncodingContainer 对应的是字典类型,形成 { super: { } } 这样的结构。
  • 如果是 UnkeyedEncodingContainer 对应的是数组结构。
  • SingleValueEncodingContainer 没有 super。

4. 指定编码父类的key

系统还提供一个方法:

swift 复制代码
 public mutating func superEncoder(forKey key: KeyedEncodingContainer<K>.Key) -> Encoder

我们可以通过调用指定key的方法,创建一个新的编码器:

swift 复制代码
 class Point3D: Point2D {
    var z = 0.0
 ​
    enum CodingKeys: CodingKey {
        case z
        case point2D
    }
     
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.z, forKey: .z)
         
        let superEncoder = container.superEncoder(forKey: .point2D)
        try super.encode(to: superEncoder)
    }
 }
 ​
 // 输出信息是:
 {
  "z" : 3,
  "point2D" : {
    "x" : 1,
    "y" : 2
  }
 }

当然我们也可以通过自定义CodingKey实现指定Key:

swift 复制代码
 class Point2D: Codable {
    var x = 0.0
    var y = 0.0
    
    struct CustomKeys: CodingKey {
        var stringValue: String
         
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
         
        init(_ stringValue: String) {
            self.stringValue = stringValue
        }
         
        var intValue: Int?
         
        init?(intValue: Int) {
            self.stringValue = ""
        }
    }
 }
 ​
 class Point3D: Point2D {
    var z = 0.0
 ​
    enum CodingKeys: CodingKey {
        case z
    }
     
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CustomKeys.self)
        try container.encode(self.z, forKey: CustomKeys.init("z"))
         
        let superEncoder = container.superEncoder(forKey: CustomKeys("point2D"))
        try super.encode(to: superEncoder)
    }
 }

八. 枚举

遵循Codable协议的枚举,也可以自动将数据转化为枚举值。

csharp 复制代码
 struct EnumFeed: Codable {
    var a: SexEnum
    var b: SexEnum
 }
 ​
 enum SexEnum: String, Codable {
    case man
    case women
 }
 ​
 let json = """
 {
    "a": "man",
    "b": "women"
 }
 """
 ​
 guard let feed = json.decode(type: EnumFeed.self) else { return }
 print(feed

1. 枚举映射失败的异常

如果未被枚举的值出现(将数据中b的值改为 "unkown"),decode的时候会抛出DecodingError。 哪怕声明为可选,一样会报错。

yaml 复制代码
 ▿ DecodingError
  ▿ dataCorrupted : Context
    ▿ codingPath : 1 element
      - 0 : CodingKeys(stringValue: "b", intValue: nil)
    - debugDescription : "Cannot initialize SexEnum from invalid String value unkown"
    - underlyingError : nil

2. 兼容方案

重写init方法,自定义映射

swift 复制代码
 struct EnumFeed: Codable {
    var a: SexEnum
    var b: SexEnum?
     
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.a = try container.decode(SexEnum.self, forKey: .a)
         
        if try container.decodeNil(forKey: .b) {
            self.b = nil
        } else {
            let bRawValue = try container.decode(String.self, forKey: .b)
            if let temp = SexEnum(rawValue: bRawValue) {
                self.b = temp
            } else {
                self.b = nil
            }
        }
    }
 }

使用协议提供映射失败的默认值

苹果官方给了一个解决办法: 使用协议提供映射失败的默认值

swift 复制代码
 public protocol SmartCaseDefaultable: RawRepresentable, Codable {
    /// 使用接收到的数据,无法用枚举类型中的任何值表示而导致解析失败,使用此默认值。
    static var defaultCase: Self { get }
 }
 ​
 public extension SmartCaseDefaultable where Self.RawValue: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(RawValue.self)
        self = Self.init(rawValue: rawValue) ?? Self.defaultCase
    }
 }

使此枚举继承本协议即可

typescript 复制代码
 enum SexEnum: String, SmartCaseDefaultable {
    case man
    case women
     
    static var defaultCase: SexEnum = .man
 }

九. 特殊格式的数据

1. 日期格式

ini 复制代码
 let json = """
 {
    "birth": "2000-01-01 00:00:01"
 }
 """
 guard let data = json.data(using: .utf8) else { return }
 let decoder = JSONDecoder()
 ​
 ​
 let dateFormatter = DateFormatter()
 dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
 decoder.dateDecodingStrategy = .formatted(dateFormatter)
 do {
    let feed = try decoder.decode(Person.self, from: data)
    print(feed)
 } catch {
    print("Error decoding: (error)")
 }
 ​
 struct Person: Codable {
    var birth: Date
 }

更多关于dateDecodingStrategy的使用请查看 DateDecodingStrategy 。

java 复制代码
 public enum DateDecodingStrategy : Sendable {
 ​
    case deferredToDate
 ​
    case secondsSince1970
 ​
    case millisecondsSince1970
 ​
    case iso8601
    
    case formatted(DateFormatter)
 ​
    @preconcurrency case custom(@Sendable (_ decoder: Decoder) throws -> Date)
 }

2. 浮点数

php 复制代码
 let json = """
 {
    "height": "NaN"
 }
 """
 ​
 struct Person: Codable {
    var height: Float
 }
 ​
 guard let data = json.data(using: .utf8) else { return }
 let decoder = JSONDecoder()
 decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+∞", negativeInfinity: "-∞", nan: "NaN")
 do {
    let feed = try decoder.decode(Person.self, from: data)
    print(feed.height)
 } catch {
    print("Error decoding: (error)")
 }
 // 输出: nan

当Float类型遇到 NaN时候,如不做特殊处理,会导致解析失败。可以使用nonConformingFloatDecodingStrategy 兼容不符合json浮点数值当情况。

更多信息请查看:

arduino 复制代码
 public enum NonConformingFloatDecodingStrategy : Sendable {
    case `throw`
    case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
 }

3. Data格式

有一个这样的base64的数据

csharp 复制代码
 let json = """
 {
    "address": "aHR0cHM6Ly93d3cucWl4aW4uY29t"
 }
 """
 struct QXBWeb: Codable {
    var address: Data
 }
 ​
 guard let data = json.data(using: .utf8) else { return }
 let decoder = JSONDecoder()
 decoder.dataDecodingStrategy = .base64
 do {
    let web = try decoder.decode(QXBWeb.self, from: data)
    guard let address = String(data: web.address, encoding: .utf8) else { return }
    print(address)
 } catch {
    print("Error decoding: (error)")
 }
 // 输出: https://www.qixin.com

更多关于dataDecodingStrategy的信息,请查看DataDecodingStrategy。

less 复制代码
 public enum DataDecodingStrategy : Sendable {
    case deferredToData
    /// 从base64编码的字符串解码' Data '。这是默认策略。
    case base64
    @preconcurrency case custom(@Sendable (_ decoder: Decoder) throws -> Data)
 }

4. URL格式

Codable可以自动将字符串映射为URL格式。

ini 复制代码
 struct QXBWeb: Codable {
    var address: URL
 }
 ​
 let json = """
 {
    "address": "https://www.qixin.com"
 }
 """
 ​
 guard let data = json.data(using: .utf8) else { return }
 let decoder = JSONDecoder()
 do {
    let web = try decoder.decode(QXBWeb.self, from: data)
    print(web.address.absoluteString)
 } catch {
    print("Error decoding: (error)")
 }

但是要注意 数据为 ""的情况。

yaml 复制代码
 ▿ DecodingError
  ▿ dataCorrupted : Context
    ▿ codingPath : 1 element
      - 0 : CodingKeys(stringValue: "address", intValue: nil)
    - debugDescription : "Invalid URL string."
    - underlyingError : nil

我们来看系统内部是如何进行解码URL的:

swift 复制代码
 if T.self == URL.self || T.self == NSURL.self {
    guard let urlString = try self.unbox(value, as: String.self) else {
        return nil
    }
     
    guard let url = URL(string: urlString) else {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
                                                                debugDescription: "Invalid URL string."))
    }
     
    decoded = (url as! T)
 }

解码URL分为两步: 1. urlString是否存在。 2. urlString是否可以转成URL。

可以使用这个方法兼容,URL没法提供默认值,只能设置为可选。

swift 复制代码
 struct QXBWeb: Codable {
    var address: URL?
     
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            let str = try container.decode(String.self, forKey: .address)
            if let url = URL(string: str) {
                self.address = url
            } else {
                self.address = nil
            }
        } catch {
            print(error)
            self.address = nil
        }
    }
 }

十. 关于嵌套结构

除了解码元素,UnkeyedDecodingContainer 和 KeyedDecodingContainer 还提供了一些其他有用的方法,例如nestedContainer(keyedBy:forKey:)和nestedUnkeyedContainer(forKey:),用于解码嵌套的容器类型。

  • nestedContainer: 用于生成解码字典类型的容器。
  • nestedUnkeyedContainer: 用于生成解码数组类型的容器。

nestedContainer的使用场景

在Swift中,nestedContainer是一种用于处理嵌套容器的方法。它是KeyedDecodingContainer和UnkeyedDecodingContainer协议的一部分,用于解码嵌套的数据结构。

swift 复制代码
 ///返回存储在给定键类型的容器中的给定键的数据。
 ///
 /// -参数类型:用于容器的键类型。
 /// -参数key:嵌套容器关联的键。
 /// -返回:一个KeyedDecodingContainer容器视图。
 /// -抛出:' DecodingError. 'typeMismatch ',如果遇到的存储值不是键控容器。
 public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey

当你需要解码一个嵌套的容器时,你可以使用nestedContainer方法。这个方法接受一个键(对于KeyedDecodingContainer)或者一个索引(对于UnkeyedDecodingContainer),然后返回一个新的嵌套容器,你可以使用它来解码嵌套的值。

我们将图左侧的数据,解码到图右侧的Class模型中。 Class 模型对应的数据结构如图所示。

我们先来创建这两个模型: Class 和 Student

swift 复制代码
 struct Student: Codable {
    let id: String
    let name: String
 }
 ​
 struct Class: Codable {
    var students: [Student] = []
 ​
    // 数据中的key结构和模型中key结构不对应,需要自定义Key
    struct CustomKeys: CodingKey {
        var intValue: Int? {return nil}
        var stringValue: String
        init?(intValue: Int) {return nil}
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
         
        init(_ stringValue: String) {
            self.stringValue = stringValue
        }
    }
 }

Class是一个模型,所以对应的应该是一个KeyedDecodingContainer容器。

swift 复制代码
 init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CustomKeys.self)
 ​
    let keys = container.allKeys
 ​
    var list: [Student] = []
    for key in keys {
        let nestedContainer = try container.nestedContainer(keyedBy: CustomKeys.self, forKey: key)
        let name = try nestedContainer.decode(String.self, forKey: .init("name"))
        let student = Student(id: key.stringValue, name: name)
 ​
        list.append(student)
    }
    self.students = list
 }

container.allKeys的值为:

php 复制代码
 ▿ 3 elements
  ▿ 0 : CustomKeys(stringValue: "2", intValue: nil)
    - stringValue : "2"
  ▿ 1 : CustomKeys(stringValue: "1", intValue: nil)
    - stringValue : "1"
  ▿ 2 : CustomKeys(stringValue: "3", intValue: nil)
    - stringValue : "3"

通过遍历allKeys得到的key,对应的数据结构是一个字典: { "name" : xxx }。

php 复制代码
 let nestedContainer = try container.nestedContainer(keyedBy: CustomKeys.self, forKey: key)
 let name = try nestedContainer.decode(String.self, forKey: .init("name"))
 let student = Student(id: key.stringValue, name: name)

这样就得Student的全部数据。id的值就是key.stringValue,name的值就是nestedContainer容器中key为"name"的解码值。

根据这个思路,我们也可以很容易的完成 encode 方法

swift 复制代码
 func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CustomKeys.self)
     
    for student in students {
        var keyedContainer = container.nestedContainer(keyedBy: CustomKeys.self, forKey: .init(student.id))
        try keyedContainer.encode(student.name, forKey: .init("name"))
    }
 }

nestedUnkeyedContainer的使用场景

nestedUnkeyedContainer 是 Swift 中的 KeyedDecodingContainer 协议的一个方法,它允许你从嵌套的无键容器中解码一个值的数组,该容器通常是从 JSON 或其他编码数据中获取的。

swift 复制代码
 /// 返回为给定键存储的数据,以无键容器的形式表示。
 public func nestedUnkeyedContainer(forKey key: KeyedDecodingContainer<K>.Key) throws -> UnkeyedDecodingContainer

有一个这样的数据结构,解码到 Person 模型中:

rust 复制代码
 let json = """
 {
     "name": "xiaoming",
     "age": 10,
     "hobbies": [
         {
             "name": "basketball",
             "year": 8
         },
         {
             "name": "soccer",
             "year": 2
         }
     ]
 }
 """
 ​
 struct Person {
     let name: String
     let age: Int
     var hobbies: [Hobby]
     
     struct Hobby {
         let name: String
         let year: Int
     }
 }
使用系统自带嵌套解析能力
rust 复制代码
 struct Person: Codable {
    let name: String
    let age: Int
    var hobbies: [Hobby]
     
    struct Hobby: Codable {
        let name: String
        let year: Int
    }
 }
使用自定义解码
swift 复制代码
 struct Person: Codable {
     let name: String
     let age: Int
     var hobbies: [Hobby]
     
     struct Hobby: Codable {
         let name: String
         let year: Int
         
         enum CodingKeys: CodingKey {
             case name
             case year
         }
     }
     
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         name = try container.decode(String.self, forKey: .name)
         age = try container.decode(Int.self, forKey: .age)
         
         // 解码嵌套的数组
         var nestedContainer = try container.nestedUnkeyedContainer(forKey: .hobbies)
         var tempHobbies: [Hobby] = []
         while !nestedContainer.isAtEnd {
             if let hobby = try? nestedContainer.decodeIfPresent(Hobby.self) {
                 tempHobbies.append(hobby)
             }
         }
         hobbies = tempHobbies
     }
 }
使用完全自定义解码
swift 复制代码
 struct Person: Codable {
     let name: String
     let age: Int
     var hobbies: [Hobby]
     
     struct Hobby: Codable {
         let name: String
         let year: Int
         
         enum CodingKeys: CodingKey {
             case name
             case year
         }
     }
     
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         name = try container.decode(String.self, forKey: .name)
         age = try container.decode(Int.self, forKey: .age)
         
         var hobbiesContainer = try container.nestedUnkeyedContainer(forKey: .hobbies)
         var tempItems: [Hobby] = []
         while !hobbiesContainer.isAtEnd {
             let hobbyContainer = try hobbiesContainer.nestedContainer(keyedBy: Hobby.CodingKeys.self)
             
             let name = try hobbyContainer.decode(String.self, forKey: .name)
             let year = try hobbyContainer.decode(Int.self, forKey: .year)
             let item = Hobby(name: name, year: year)
             tempItems.append(item)
         }
         hobbies = tempItems
     }
 }

十一. 其他一些小知识点

1. 模型中属性的didSet方法不会执行

swift 复制代码
 struct Feed: Codable {
    init() {
         
    }
 ​
    var name: String = "" {
        didSet {
            print("被set了")
        }
    }
     
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
    }
 }
 ​
 let json = """
 {
    "name": "123"
 }
 """
 ​
 guard let feed = json.decode(type: Feed.self) else { return }
 print(feed)

在Swift中,使用Codable解析完成后,模型中的属性的didSet方法不会执行。

这是因为didSet方法只在属性被直接赋值时触发,而不是在解析过程中。

Codable协议使用编码和解码来将数据转换为模型对象,而不是通过属性的直接赋值来触发didSet方法。

如果您需要在解析完成后执行特定的操作,您可以在解析后手动调用相应的方法或者使用自定义的初始化方法来处理。

相关推荐
黑化旺仔7 分钟前
【OC】Runtime:类元数据、加载初始化与消息派发
开发语言·macos·ios·objective-c
songgeb44 分钟前
iOS App支持设置RTL语言的实践
ios·swift·ui kit
软泡芙1 小时前
【iOS】swift启动屏显示黑屏
ios
用户69371750013843 小时前
当你向领导申请 AI 编程工具预算时,如果领导追问“能提效多少,有量化数据吗”,你该怎么回答?
android·后端·ios
Layer3 小时前
一些脱离 Mac 依赖的 Agent 操作 iPhone 实现
ios·ai编程·测试
用户69371750013844 小时前
当我说 Claude 搭配 GLM-5.3-Flash 太好用了,同事却提醒我:你可能正在失去对代码的掌控
android·后端·ios
茶底世界之下5 小时前
一键裁方会丢掉什么?用 Awaking 给园林照片做两种画幅
ios
Marco_Marco1356 小时前
定位SDK接入实战:Android/iOS/鸿蒙三端怎么选、怎么接
android·ios·harmonyos
老李IT笔记21 小时前
关掉设备上的 AI 扩展:两个键各管一层,配错了等于没配
人工智能·ios·智能手机
2501_9159214321 小时前
iOS应用耗电测试:使用Energy Log、Keymob和Instruments进行性能监控与能源消耗分析
android·ios·小程序·https·uni-app·iphone·webview