一、Codable协议详解
1. Codable本质
swift
// Codable是Encodable和Decodable的类型别名
public typealias Codable = Encodable & Decodable
// 标准库中的基础类型默认遵循Codable
// Int, String, Double, Bool, Date, Data, URL等
2. 基本用法
swift
struct User: Codable {
let id: Int
let name: String
let email: String
let createdAt: Date
// 自定义键名
enum CodingKeys: String, CodingKey {
case id, name, email
case createdAt = "created_at"
}
}
// 编解码示例
let jsonData = """
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"created_at": "2024-01-13T10:00:00Z"
}
""".data(using: .utf8)!
// 解码
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let user = try decoder.decode(User.self, from: jsonData)
// 编码
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedData = try encoder.encode(user)
3. 高级特性
swift
// 条件编码
struct Product: Codable {
let id: Int
let name: String
var price: Double?
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
// 只编码非nil值
try container.encodeIfPresent(price, forKey: .price)
}
}
// 继承Codable的类
class Vehicle: Codable {
var brand: String
var year: Int
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
brand = try container.decode(String.self, forKey: .brand)
year = try container.decode(Int.self, forKey: .year)
}
}
二、字典(Dictionary)详解
1. 基本特性
swift
// 类型声明
var dict1: [String: Any] = [:] // 任意值类型
var dict2: [Int: String] = [:] // 键值类型确定
var dict3: Dictionary<String, Double> = [:] // 完整语法
// 增删改查
var userInfo: [String: Any] = [
"name": "李四",
"age": 25,
"scores": [85, 90, 78]
]
// 访问
if let name = userInfo["name"] as? String {
print(name)
}
// 更新
userInfo["age"] = 26
userInfo.updateValue(["math": 90, "english": 85], forKey: "scores")
// 删除
userInfo.removeValue(forKey: "age")
userInfo["scores"] = nil
2. 字典与Codable
swift
// 字典直接编解码
let scoresDict = ["math": 95, "english": 88, "history": 92]
let dictData = try JSONEncoder().encode(scoresDict)
let decodedDict = try JSONDecoder().decode([String: Int].self, from: dictData)
// 包含复杂类型的字典
struct Config: Codable {
let settings: [String: Any] // ❌ 编译错误:Any不符合Codable
// 解决方案1:使用具体类型
let typedSettings: [String: String]
// 解决方案2:自定义编解码
enum CodingKeys: String, CodingKey {
case typedSettings
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
typedSettings = try container.decode([String: String].self, forKey: .typedSettings)
}
}
3. 字典的性能特点
-
查找时间:O(1)平均,O(n)最坏
-
内存开销:较高(需要存储hash表)
-
顺序性:无序(Swift 5后默认保持插入顺序)
三、数组(Array)详解
1. 基本特性
swift
// 创建数组
var numbers: [Int] = []
var names = ["张三", "李四"] // 类型推断
var matrix = [[1, 2], [3, 4]] // 二维数组
// 基本操作
numbers.append(1) // 追加
numbers.insert(0, at: 0) // 插入
numbers.remove(at: 1) // 删除
let first = numbers.first // 首元素
let last = numbers.last // 尾元素
// 高阶函数
let doubled = numbers.map { $0 * 2 }
let evenNumbers = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0, +)
2. 数组与Codable
swift
// 基础类型数组
let numbers = [1, 2, 3, 4, 5]
let arrayData = try JSONEncoder().encode(numbers)
let decodedArray = try JSONDecoder().decode([Int].self, from: arrayData)
// 自定义对象数组
struct Task: Codable {
let id: Int
let title: String
let completed: Bool
}
let tasks = [
Task(id: 1, title: "学习Swift", completed: true),
Task(id: 2, title: "完成项目", completed: false)
]
// 编解码整个数组
let tasksData = try JSONEncoder().encode(tasks)
let decodedTasks = try JSONDecoder().decode([Task].self, from: tasksData)
3. 数组的性能特点
-
随机访问:O(1)
-
尾部追加:O(1)平摊
-
头部/中间插入删除:O(n)
-
内存:连续存储,缓存友好
四、三者的详细比较
1. 数据结构对比
| 特性 | Codable协议 | 字典(Dictionary) | 数组(Array) |
|---|---|---|---|
| 主要用途 | 数据序列化/反序列化 | 键值对存储 | 有序集合存储 |
| 内存布局 | 协议不决定布局 | 哈希表(分散) | 连续内存块 |
| 访问方式 | 通过编解码器 | 键访问 | 索引访问 |
| 顺序性 | 编码时决定 | 插入顺序(Swift 5+) | 严格保持顺序 |
| 类型安全 | ✅ 编译时检查 | ✅ 泛型约束 | ✅ 泛型约束 |
2. 性能对比
| 操作 | 字典 | 数组 | Codable处理 |
|---|---|---|---|
| 查找元素 | O(1)平均 | O(1)(按索引) | O(n)(线性解析) |
| 插入元素 | O(1)平均 | O(n)(中间插入) | N/A |
| 删除元素 | O(1)平均 | O(n)(中间删除) | N/A |
| 内存占用 | 较高 | 较低 | 额外编解码开销 |
| JSON解析 | 需要类型转换 | 直接支持 | 专门优化 |
3. Codable与集合的交互
swift
// 1. 字典和数组组合的Codable模型
struct APIResponse: Codable {
let status: String
let data: [String: [User]] // 字典值包含数组
let pagination: Pagination
struct Pagination: Codable {
let currentPage: Int
let totalPages: Int
}
}
// 2. 动态键名处理
struct DynamicKeysModel: Codable {
var allItems: [String: Item]
struct Item: Codable {
let value: Int
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var items = [String: Item]()
for key in container.allKeys {
let item = try container.decode(Item.self, forKey: key)
items[key.stringValue] = item
}
allItems = items
}
struct DynamicCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
return nil // 不需要处理数字键
}
}
}
// 3. 性能优化:懒解码
class LazyDecodableArray<T: Decodable>: Decodable {
private var storage: Data?
private var decodedArray: [T]?
var array: [T] {
mutating get {
if let decoded = decodedArray {
return decoded
}
guard let data = storage else { return [] }
let array = try! JSONDecoder().decode([T].self, from: data)
decodedArray = array
return array
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
storage = try container.decode(Data.self)
}
}
4. 实际应用场景
swift
// 场景1:网络响应解析(最常用)
struct NetworkService {
func fetchUsers() async throws -> [User] {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
func fetchUserDictionary() async throws -> [String: User] {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([String: User].self, from: data)
}
}
// 场景2:本地存储
struct StorageManager {
static func save<T: Codable>(_ items: [T], forKey key: String) {
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: key)
}
}
static func load<T: Codable>(_ type: [T].Type, forKey key: String) -> [T]? {
guard let data = UserDefaults.standard.data(forKey: key) else {
return nil
}
return try? JSONDecoder().decode([T].self, from: data)
}
}
// 场景3:配置管理
struct AppConfig: Codable {
// 使用字典存储动态配置项
let featureFlags: [String: Bool]
// 使用数组存储有序配置
let launchScreens: [String]
// 嵌套Codable对象
let apiEndpoints: [String: Endpoint]
struct Endpoint: Codable {
let url: String
let method: String
let headers: [String: String]?
}
}
五、最佳实践建议
1. 选择准则
-
使用数组的场景:
-
有序数据集合
-
需要频繁遍历
-
数据量较大且需要连续内存
-
-
使用字典的场景:
-
需要通过键快速查找
-
数据关系是键值对形式
-
键的数量相对稳定
-
-
使用Codable的场景:
-
所有需要持久化或传输的数据结构
-
网络API响应解析
-
配合UserDefaults、CoreData等使用
-
2. 性能优化技巧
swift
// 1. 使用原生类型而非Codable包装
// 推荐:
let scores: [String: Int] = [...]
// 避免:
struct WrappedScores: Codable {
let scores: [String: Int]
}
// 2. 批量编解码
extension Array where Element: Codable {
func saveToDisk() throws {
let data = try JSONEncoder().encode(self)
try data.write(to: fileURL)
}
static func loadFromDisk() throws -> Self {
let data = try Data(contentsOf: fileURL)
return try JSONDecoder().decode(Self.self, from: data)
}
}
// 3. 使用JSONSerialization处理部分数据
func parseLargeJSON() {
if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let items = json["items"] as? [[String: Any]] {
// 只解码需要的部分
let importantItems = items.compactMap { dict -> Item? in
guard let id = dict["id"] as? Int else { return nil }
return Item(id: id, rawData: dict)
}
}
}
3. 错误处理
swift
enum DecodingError: Error {
case missingKey(String)
case typeMismatch
}
func safeDecode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
let decoder = JSONDecoder()
// 配置解码策略
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
do {
return try decoder.decode(type, from: data)
} catch let DecodingError.keyNotFound(key, context) {
print("缺少键: \(key), 路径: \(context.codingPath)")
throw DecodingError.missingKey(key.stringValue)
} catch let DecodingError.typeMismatch(type, context) {
print("类型不匹配: \(type), 路径: \(context.codingPath)")
throw DecodingError.typeMismatch
}
}
六、总结
-
Codable协议是Swift数据序列化的核心,为字典和数组提供了统一的编解码能力
-
字典 适合键值查找,数组适合有序访问,两者结合能处理大多数数据场景
-
在实际开发中,三者经常结合使用:用Codable定义模型,用字典存储配置,用数组管理集合
-
性能方面需要注意:数组连续内存访问快,字典哈希查找快,Codable编解码有额外开销
-
根据具体场景选择合适的数据结构,并利用Swift的类型安全特性保证代码质量
正确理解和使用这三者,能显著提升iOS应用的代码质量、性能和可维护性。