【IOS】Codable

文章目录

一、Codable 是什么?

Codable 是 Apple 在 Swift 4.0 中引入的一套原生编解码协议 ,它是一个类型别名(typealias Codable = Encodable & Decodable),让你的 Swift 数据模型能在内存对象外部表示格式(如 JSON、Plist、XML、PropertyList)之间相互转换。

在 Codable 出现之前,iOS 开发者普遍使用 JSONSerialization 手动解析字典,或者依赖第三方库(如 SwiftyJSON、ObjectMapper)。Codable 的出现,让 Swift 拥有了类型安全、声明式、零依赖的原生解析能力,目前已是 Swift 网络层的标配。


二、核心组件

组件 作用
Encodable 将 Swift 对象编码为外部数据(如 JSON 二进制)
Decodable 将外部数据解码为 Swift 对象
Codable 同时具备编码和解码能力
JSONEncoder 将 Codable 对象编码为 JSON Data
JSONDecoder 将 JSON Data 解码为 Codable 对象
CodingKeys 自定义属性与 JSON 字段的映射关系

三、基础用法(80% 的场景)

3.1 自动解析(最常用,零额外代码)

假设服务端返回如下 JSON:

json 复制代码
{
    "id": 1001,
    "name": "张伟",
    "isVIP": true,
    "score": 98.5,
    "tags": ["学霸", "篮球"]
}

Swift 模型只要声明 Codable,所有属性类型匹配即可自动完成转换:

swift 复制代码
import Foundation

struct User: Codable {
    let id: Int
    let name: String
    let isVIP: Bool
    let score: Double
    let tags: [String]
}

// 解码(网络请求的 Data 转模型)
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)
print(user.name)  // 输出:张伟

// 编码(模型转 JSON Data)
let encoder = JSONEncoder()
let encodedData = try encoder.encode(user)
let encodedString = String(data: encodedData, encoding: .utf8)!

3.2 可选值处理(应对字段缺失)

如果 JSON 中某些字段可能不存在值为 null,使用可选类型:

swift 复制代码
struct User: Codable {
    let id: Int
    let name: String
    let email: String?        // 可选:字段可能缺失
    let avatarURL: URL?       // URL 类型自动转换
}

四、自定义映射(CodingKeys)

当 Swift 属性名与 JSON 字段名不一致时(最常见的是 snake_case 与 camelCase ),使用 CodingKeys 枚举映射。

场景:JSON 使用下划线,Swift 使用驼峰

json 复制代码
{
    "user_id": 1001,
    "first_name": "张",
    "last_name": "伟"
}
swift 复制代码
struct User: Codable {
    let userId: Int
    let firstName: String
    let lastName: String
    
    enum CodingKeys: String, CodingKey {
        case userId = "user_id"
        case firstName = "first_name"
        case lastName = "last_name"
    }
}

💡 捷径 :如果 JSON 字段全是 snake_case,可以在 JSONDecoder 上设置全局转换策略,省去写 CodingKeys 的麻烦:

swift 复制代码
decoder.keyDecodingStrategy = .convertFromSnakeCase

同理,编码时用 encoder.keyEncodingStrategy = .convertToSnakeCase


五、日期处理(高频痛点)

JSON 中的日期通常是字符串,Swift 中要转为 DateJSONDecoder 提供了多种内置策略:

JSON 日期格式 Decoder 策略
"2026-08-06T10:30:00Z" .iso8601
时间戳(秒)1712345678 .secondsSince1970
时间戳(毫秒)1712345678000 .millisecondsSince1970
自定义格式 .formatted(DateFormatter)
swift 复制代码
// 内置 ISO8601(最常用)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

// 自定义格式(如 "2026-08-06 10:30:00")
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(formatter)

struct Event: Codable {
    let title: String
    let startTime: Date   // 自动按策略转换
}

六、进阶:自定义解码逻辑(init(from:)

当 JSON 结构嵌套复杂 ,或需要额外处理/校验时,手动实现解码器。

场景:服务端返回嵌套对象,但你想扁平化

json 复制代码
{
    "id": 1001,
    "user": {
        "name": "王芳",
        "age": 28
    },
    "department": "技术部"
}

希望模型只保留 userIduserNamedepartment

swift 复制代码
struct Employee: Codable {
    let userId: Int
    let userName: String
    let department: String
    
    enum CodingKeys: String, CodingKey {
        case userId = "id"
        case user           // 嵌套容器Key
        case department
    }
    
    // 子层级 Key
    enum UserCodingKeys: String, CodingKey {
        case name
        case age
    }
    
    // 自定义解码
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.userId = try container.decode(Int.self, forKey: .userId)
        self.department = try container.decode(String.self, forKey: .department)
        
        // 从嵌套层取出 name
        let userContainer = try container.nestedContainer(keyedBy: UserCodingKeys.self, forKey: .user)
        self.userName = try userContainer.decode(String.self, forKey: .name)
    }
}

场景:字符串转枚举(服务端返回数字枚举)

json 复制代码
{ "status": 2 }
swift 复制代码
enum OrderStatus: Int, Codable {
    case pending = 1
    case shipped = 2
    case delivered = 3
}

struct Order: Codable {
    let status: OrderStatus  // 自动将 Int 2 转为 .shipped
}

如果是字符串枚举"shipped"),直接声明 String, Codable 即可自动转换。


七、应对"脏数据"(类型不匹配)

服务端可能返回 "123" 表示 ID,而模型需要 Int。Codable 默认会抛错,需要手动实现转换:

swift 复制代码
struct Product: Codable {
    let id: Int
    let price: Double
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 处理 ID:可能是 String,也可能是 Int
        if let idInt = try? container.decode(Int.self, forKey: .id) {
            self.id = idInt
        } else {
            let idString = try container.decode(String.self, forKey: .id)
            self.id = Int(idString) ?? 0
        }
        
        // 处理价格:可能返回 "19.99"
        if let priceDouble = try? container.decode(Double.self, forKey: .price) {
            self.price = priceDouble
        } else {
            let priceString = try container.decode(String.self, forKey: .price)
            self.price = Double(priceString) ?? 0.0
        }
    }
    
    enum CodingKeys: String, CodingKey {
        case id, price
    }
}

八、继承与类(注意点)

当使用类继承 时,子类必须实现 required init(from:) 并在解码时调用父类方法:

swift 复制代码
class Animal: Codable {
    var name: String
    enum CodingKeys: String, CodingKey { case name }
}

class Dog: Animal {
    var breed: String
    
    enum CodingKeys: String, CodingKey { case breed }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.breed = try container.decode(String.self, forKey: .breed)
        try super.init(from: decoder)  // 关键:解码父类
    }
}

建议 :在 Swift 开发中,优先使用 struct 而非 class 来定义模型,能避免大量继承带来的编解码复杂度。


九、编码技巧(生成 JSON 请求体)

swift 复制代码
struct LoginRequest: Codable {
    let username: String
    let password: String
}

let request = LoginRequest(username: "test", password: "123456")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted   // 美化输出(便于调试)
let data = try encoder.encode(request)

// 直接放入 URLRequest 的 httpBody
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = data
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

十、常见陷阱与最佳实践

陷阱 解决方案
属性是 let 常量,且服务端字段缺失 改用 var 并赋默认值,或声明为可选 ?
动态 Key(如 "data_123" 实现 init(from:) 遍历 container.allKeys 手动解析
顶层 JSON 是数组而非字典 decode([Model].self, from: data) 即可
模型包含 @Published(SwiftUI) @Published 会破坏 Codable 自动合成,需手动实现 init(from:) 或使用 @propertyWrapper 的自定义解码
超大 JSON 解析性能 使用 JSONDecoder() 时设置 userInfo 上下文,或考虑增量解析(极少场景)

十一、与 Combine 结合(黄金搭档)

Combine 的 decode 操作符天然支持 Codable:

swift 复制代码
import Combine

func fetchUsers() -> AnyPublisher<[User], Error> {
    let url = URL(string: "https://api.example.com/users")!
    return URLSession.shared.dataTaskPublisher(for: url)
        .map(\.data)
        .decode(type: [User].self, decoder: JSONDecoder())  // 一行解析
        .eraseToAnyPublisher()
}

总结

阶段 掌握内容
入门 结构体声明 Codable,用 JSONDecoder 一行解析,处理可选值
进阶 CodingKeys 映射,日期策略,init(from:) 处理嵌套结构
精通 应对脏数据转换,动态 Key 解析,自定义编码器,与 Combine/SwiftUI 无缝集成

一句话心法 :Codable 的核心理念是"用 Swift 的类型系统描述 JSON 的结构"。只要模型写得准,解析就是一行代码的事。遇到复杂 JSON,优先用 QuickType 在线工具生成 Swift 模型代码,再微调映射逻辑,效率极高!

相关推荐
nvvas5 小时前
9月苹果发布会前瞻:新iPhone、Apple Watch重点速览
ios·iphone
枝枝在Coding5 小时前
跳板机明明能连上,为什么内网服务器还是进不去?
ssh
Zender Han6 小时前
Flutter 自适应(Adaptive)与响应式(Responsive)设计实践:官方推荐方案详解
android·flutter·ios
冯汉栩7 小时前
Swift Control DateSelection(日期选择框)
ios·cocoa·swift
开心就好20259 小时前
appuploader-cli 使用教程:在 Windows 上用命令行把 IPA 上传到 App Store
后端·ios
软泡芙10 小时前
【IOS】CoreBluetooth
ios
冯汉栩10 小时前
Swift Control DashLineView(虚线)
开发语言·ios·swift
Codeking__11 小时前
iOS 动画:core Animation 底层原理
macos·objective-c·cocoa
2501_9159184111 小时前
iOS 真机调试工具推荐,安装运行、性能分析有哪些工具
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程