一、核心知识点罗列
(一)编码和解码的核心协议:Codable
- 协议组成
Codable是复合协议,由Encodable(编码)和Decodable(解码)组合而成,遵循Codable即同时支持编码与解码。- 核心方法:
Encodable:func encode(to encoder: Encoder) throws,将实例编码为外部格式(如JSON、PropertyList)。Decodable:init(from decoder: Decoder) throws,从外部格式解码为实例。
- 自动遵循协议的条件
- 结构体/枚举/类的所有存储属性均遵循
Codable,编译器会自动合成Encodable和Decodable的实现,无需手动编写。 - 支持的基础类型:
Int、String、Bool等标准库类型,以及Array、Dictionary、Set等集合类型(元素需遵循Codable)。 - 枚举特殊要求:带关联值的枚举需关联值遵循
Codable;无关联值的枚举默认自动遵循。
- 结构体/枚举/类的所有存储属性均遵循
(二)编码(Encoding)流程
- 核心逻辑
- 通过
JSONEncoder(JSON格式)或PropertyListEncoder(属性列表格式)等编码器,调用实例的encode(to:)方法,将实例转换为外部数据格式。 - 编码步骤:
- 编码器创建编码容器(键值容器、无键容器、单值容器)。
- 实例将自身属性写入对应容器。
- 编码器将容器数据转换为目标格式(如JSON数据)。
- 通过
- 常用编码器配置
outputFormatting:设置输出格式(如JSON的缩进、排序键)。keyEncodingStrategy:配置键的编码策略(如蛇形命名、自定义键映射)。dateEncodingStrategy:设置日期类型的编码方式(如时间戳、ISO8601字符串)。
(三)解码(Decoding)流程
- 核心逻辑
- 通过
JSONDecoder或PropertyListDecoder等解码器,读取外部数据,调用实例的init(from:)初始化方法,重建实例。 - 解码步骤:
- 解码器从外部数据中创建解码容器。
- 实例从容器中读取属性值并初始化。
- 处理可选值、默认值等特殊场景(如属性缺失时的容错)。
- 通过
- 常用解码器配置
- 与编码器对应,支持
keyDecodingStrategy(键解码策略)、dateDecodingStrategy(日期解码策略)等,需与编码配置一致。
- 与编码器对应,支持
(四)编码容器与数据写入/读取
- 容器类型
- 键值容器(KeyedEncodingContainer/KeyedDecodingContainer):适用于结构体、类等带命名属性的类型,通过
CodingKeys映射属性与外部键名。 - 无键容器(UnkeyedEncodingContainer/UnkeyedDecodingContainer):适用于数组等有序集合类型,按顺序写入/读取元素。
- 单值容器(SingleValueEncodingContainer/SingleValueDecodingContainer):适用于仅含单个值的类型(如包装类型
struct Wrapper<T: Codable> { let value: T })。
- 键值容器(KeyedEncodingContainer/KeyedDecodingContainer):适用于结构体、类等带命名属性的类型,通过
- 容器操作核心方法
- 写入:
encode(_:forKey:)(键值容器)、encode(_:)(无键/单值容器)。 - 读取:
decode(_:forKey:)(键值容器)、decode(_:)(无键/单值容器)。 - 可选值处理:
decodeIfPresent(_:forKey:),属性缺失时返回nil,避免解码失败。
- 写入:
(五)CodingKeys:键映射与自定义
- 核心作用
- 显式指定属性与外部数据键名的映射关系,解决“属性名与外部键名不一致”问题(如Swift属性用驼峰式,JSON用蛇形命名)。
- 语法与规则
- 定义嵌套枚举
enum CodingKeys: String, CodingKey,case名对应属性名,rawValue对应外部键名。 - 未包含在
CodingKeys中的属性,默认不参与编码和解码(需手动处理或添加CodingKeyscase)。 - 示例:
struct Person: Codable { let userName: String let userAge: Int enum CodingKeys: String, CodingKey { case userName = "user_name" case userAge = "user_age" } }
- 定义嵌套枚举
(六)自定义编码和解码实现
- 手动实现场景
- 属性名与外部键名无法通过
CodingKeys简单映射(如动态键名)。 - 需要自定义数据转换(如将JSON字符串转换为自定义枚举)。
- 处理复杂嵌套结构、默认值填充、数据校验等场景。
- 属性名与外部键名无法通过
- 手动实现示例
- 自定义编码:
struct Product: Codable { let id: String let price: Double let discountPrice: Double? func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(price, forKey: .price) // 自定义逻辑:折扣价为nil时,编码为原价的8折 let finalDiscountPrice = discountPrice ?? price * 0.8 try container.encode(finalDiscountPrice, forKey: .discountPrice) } enum CodingKeys: String, CodingKey { case id, price, discountPrice = "discount_price" } } - 自定义解码:
init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) price = try container.decode(Double.self, forKey: .price) // 容错逻辑:折扣价小于0时,设为nil discountPrice = try container.decodeIfPresent(Double.self, forKey: .discountPrice) if let discount = discountPrice, discount < 0 { discountPrice = nil } }
- 自定义编码:
(七)枚举的编码和解码
- 无关联值枚举
- 自动遵循
Codable,默认编码为原始值(需指定原始值类型,如enum Status: String, Codable { case active, inactive })。
- 自动遵循
- 带关联值枚举
- 关联值需遵循
Codable,编译器自动合成编码解码逻辑,编码后格式为字典(包含case键和关联值键)。 - 自定义关联值编码格式:需手动实现
encode(to:)和init(from:),如将关联值直接编码为单值。
- 关联值需遵循
(八)复杂场景处理
- 让第三方类型遵循Codable
- 第三方类型未遵循
Codable时,通过扩展手动实现Encodable和Decodable,或通过“包装器模式”封装(如struct WrappedThirdParty: Codable { let value: ThirdPartyType; ... })。
- 第三方类型未遵循
- 类的Codable实现
- 类需确保所有存储属性遵循
Codable,继承自NSObject的类需避免@objc属性冲突,必要时手动实现编码解码。
- 类需确保所有存储属性遵循
- 解码多态集合
- Codable默认不支持多态(如
[Animal]包含Dog和Cat实例),需自定义实现:- 方案1:用枚举包装多态类型(
enum AnimalType: Codable { case dog(Dog), cat(Cat) })。 - 方案2:编码时添加“类型标识”字段,解码时根据标识动态创建对应实例。
- 方案1:用枚举包装多态类型(
- Codable默认不支持多态(如
(九)编码和解码的常见任务
- JSON与实例互转
- 编码:
JSONEncoder().encode(instance)→Data。 - 解码:
JSONDecoder().decode(InstanceType.self, from: data)→ 实例。
- 编码:
- 属性列表(PropertyList)互转
- 使用
PropertyListEncoder和PropertyListDecoder,适用于本地配置存储等场景。
- 使用
- 默认值填充
- 解码时通过
decodeIfPresent读取可选值,结合nil合并运算符设置默认值(如name = try container.decodeIfPresent(String.self, forKey: .name) ?? "匿名")。
- 解码时通过
二、重点知识点总结
(一)Codable协议的核心价值:类型安全的序列化
- 简化序列化逻辑:编译器自动合成实现,无需手动处理JSON与模型的映射,减少模板代码。
- 类型安全保障:编码解码过程中通过编译时类型检查,避免“键名拼写错误”“类型不匹配”等运行时问题。
- 多格式支持:统一适配JSON、PropertyList等多种外部格式,通过不同编码器/解码器切换,接口一致。
(二)自动合成与CodingKeys的灵活运用
- 自动合成的条件:存储属性全遵循
Codable是自动合成的前提,结构体、枚举、类均适用(类需无自定义初始化器冲突)。 - CodingKeys的核心作用:
- 映射键名:解决Swift属性名与外部数据键名不一致问题。
- 筛选属性:未包含在
CodingKeys中的属性不参与编码解码,适用于临时属性、计算属性。
(三)自定义编码解码的核心场景
- 数据转换:如日期格式转换、数值单位转换、枚举与原始值映射。
- 容错处理:如属性缺失时填充默认值、无效数据过滤(如负数价格设为nil)。
- 复杂结构适配:如嵌套JSON的扁平化、动态键名的处理(如键名包含日期)。
(四)复杂类型的编码解码策略
- 枚举:带关联值的枚举需注意编码格式,必要时手动实现以适配外部数据结构。
- 多态集合:通过枚举包装或类型标识字段,解决Codable默认不支持多态的问题。
- 第三方类型:优先使用扩展手动实现
Codable,避免修改第三方源码,保持代码解耦。
三、难点知识点总结
(一)容器操作的逻辑梳理
- 难点:编码解码时需根据数据结构选择正确的容器类型(键值/无键/单值),容器嵌套时容易混淆层级关系。
- 常见陷阱:
- 误将无键容器当作键值容器使用(如数组编码时用
keyedContainer)。 - 键值容器的
CodingKeys与写入/读取的键不匹配,导致编码解码失败。
- 误将无键容器当作键值容器使用(如数组编码时用
- 解决方案:明确数据结构(如JSON是对象还是数组),按“外部格式→容器类型→属性映射”的逻辑逐步实现。
(二)多态集合的解码实现
- 难点:Codable解码时需提前确定目标类型,无法直接解码多态集合(如
[Animal]包含不同子类实例),手动实现时需处理类型判断与实例创建的逻辑。 - 示例陷阱:直接解码
[Animal].self时,解码器无法区分Dog和Cat,导致解码失败。 - 解决方案:
- 枚举包装法:用枚举统一多态类型,解码后再拆包(如
enum AnimalType: Codable { case dog(Dog), cat(Cat) })。 - 类型标识法:编码时添加
type字段(如"type": "dog"),解码时先读取type,再动态解码为对应类型。
- 枚举包装法:用枚举统一多态类型,解码后再拆包(如
(三)自定义编码解码的边界处理
- 难点:手动实现时需覆盖所有属性的编码解码,处理可选值、默认值、无效数据等边界场景,容易遗漏或逻辑错误。
- 常见问题:
- 遗漏部分属性的编码/解码,导致数据丢失。
- 未处理
nil值或无效数据(如字符串转整数失败),导致解码崩溃。
- 解决方案:
- 编码时按
CodingKeys逐一确认属性,避免遗漏。 - 解码时优先使用
decodeIfPresent处理可选值,结合do-catch捕获类型转换错误,添加容错逻辑。
- 编码时按
(四)与Objective-C类型的兼容
- 难点:继承自
NSObject的类遵循Codable时,可能因@objc属性、动态派发等特性导致编码解码异常。 - 常见冲突:
NSObject的description等属性与CodingKeys冲突。- 动态属性(
dynamic)的编码解码行为与Swift原生属性不一致。
- 解决方案:
- 显式定义
CodingKeys,排除不需要编码的NSObject继承属性。 - 避免在
Codable类中过度使用@objc和dynamic,必要时手动实现编码解码逻辑。
- 显式定义
(五)嵌套复杂结构的适配
- 难点:外部数据(如JSON)嵌套层级较深时,编码解码需逐层处理容器,逻辑繁琐且容易出错。
- 示例场景:JSON包含多层嵌套对象(如
{ "user": { "profile": { "name": "张三" } } })。 - 解决方案:
- 按嵌套层级定义对应的模型结构体,使模型结构与外部数据结构一致。
- 编码解码时按“外层容器→内层容器→属性”的顺序逐层操作,避免跨层级访问容器。
四、总结
本章核心围绕Codable协议展开,核心逻辑是“通过协议抽象统一编码解码接口,编译器自动合成简化常规场景,手动实现适配复杂需求”。重点在于掌握Codable的自动合成条件、CodingKeys的键映射、容器的正确使用,以及自定义编码解码的核心场景;难点集中在多态集合的处理、容器操作的逻辑梳理、边界场景的容错,以及与复杂类型(如第三方类型、Objective-C类)的兼容。
实际开发中,应遵循“优先自动合成,必要时手动实现”的原则:
- 简单模型(属性名与外部键名一致、无复杂逻辑)依赖编译器自动合成,提升开发效率。
- 复杂场景(键名映射、数据转换、容错处理)通过
CodingKeys和手动实现encode(to:)/init(from:)解决。 - 多态、嵌套结构等难点场景,采用枚举包装、分层建模等策略,平衡代码简洁性与兼容性。
通过掌握这些知识点,可实现类型安全、高效简洁的序列化逻辑,避免传统手动解析JSON的繁琐与风险。
如果需要,我可以帮你整理编码解码核心API对比表,或针对某个难点(如多态集合解码、动态键名处理)提供详细代码示例。当前文件内容过长,豆包只阅读了前 14%。