1. SmartCodable 导包
CocoaPods 安装
| 版本 | 安装方式 | 平台要求 | 继承功能支持 |
|---|---|---|---|
| 基础版 | pod 'SmartCodable' |
iOS 12+ tvOS 12+ osx10.13+ watchOS 5.0+ visionos 1.0+
|
❌ 否 |
| 继承版 | pod 'SmartCodable/Inherit' |
iOS 13+ macOS 11+
|
✅ 是 |
⚠️ 重要提示:
如果你没有强烈的继承需求,推荐使用基础版
继承功能需要 Swift 宏支持,Xcode 15+ 和 Swift 5.9+
2. 基础用法
遵循 SmartCodable 协议,类需要实现空初始化器:
class BasicTypes: SmartCodable {
var int: Int = 2
var doubleOptional: Double?
required init() {}
}
let model = BasicTypes.deserialize(from: json)
对于结构体,编译器会提供默认的空初始化器:
struct BasicTypes: SmartCodable {
var int: Int = 2
var doubleOptional: Double?
}
let model = BasicTypes.deserialize(from: json)
1. 多格式输入支持
| 输入类型 | 使用示例 | 内部转换 |
|---|---|---|
| 字典/数组 | Model.deserialize(from: dict或arr) |
直接处理原生集合 |
| JSON字符串 | Model.deserialize(from: jsonString) |
通过UTF-8转换为Data
|
| 二进制数据 | Model.deserialize(from: data) |
直接处理 |
2. 深度路径解析(designatedPath)
// JSON结构:
{
"data": {
"user": {
"info": { ...目标内容... }
}
}
}
// 访问嵌套数据:
Model.deserialize(from: json, designatedPath: "data.user.info")
3. 解码策略(options)
let options: Set<SmartDecodingOption> = [
.key(.convertFromSnakeCase),
.date(.iso8601),
.data(.base64)
]
| 策略类型 | 可用选项 | 描述 |
|---|---|---|
| 键解码 | .fromSnakeCase |
蛇形命名→驼峰命名 |
.firstLetterLower |
"FirstName"→"firstName" | |
.firstLetterUpper |
"firstName"→"FirstName" | |
| 日期解码 |
.iso8601, .secondsSince1970等 |
完整Codable日期策略 |
| 数据解码 | .base64 |
二进制数据处理 |
| 浮点数解码 |
.convertToString, .throw
|
NaN/∞处理 |
⚠️ 重要: 每种策略类型只允许一个选项(重复时最后一个生效)
2.2 解码成功后调用的后处理回调
struct Model: SmartCodable {
var name: String = ""
mutating func didFinishMapping() {
name = "我是 \(name)"
}
}
2.3 键转换
定义解码时的键映射转换,优先使用第一个有效映射:
struct Model: SmartCodable {
var name: String = ""
var age: Int?
static func mappingForKey() -> [SmartKeyTransformer]? {
[
CodingKeys.name <--- ["nickName", "realName"],
CodingKeys.age <--- "stu_age",
]
}
}
2.4 值转换
在JSON值和自定义类型间转换
内置值转换器
| 转换器 | JSON类型 | 对象类型 | 描述 |
|---|---|---|---|
| SmartDataTransformer | String | Data | Base64字符串和Data对象间转换 |
| SmartDateTransformer | Any | Date | 处理多种日期格式(时间戳,DateFormat)转Date对象 |
| SmartURLTransformer | String | URL | 字符串转URL,可选编码和添加前缀 |
struct Model: SmartCodable {
...
static func mappingForValue() -> [SmartValueTransformer]? {
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
return [
CodingKeys.url <--- SmartURLTransformer(prefix: "https://"),
CodingKeys.date1 <--- SmartDateTransformer(strategy: .timestamp),
CodingKeys.date2 <--- SmartDateTransformer(strategy: .formatted(format))
]
}
}
如果需要额外解析规则,可以自己实现Transformer。遵循ValueTransformable协议实现要求:
public protocol ValueTransformable {
associatedtype Object
associatedtype JSON
/// 从'json'转换到'object'
func transformFromJSON(_ value: Any?) -> Object?
/// 从'object'转换到'json'
func transformToJSON(_ value: Object?) -> JSON?
}
内置快速转换器辅助
static func mappingForValue() -> [SmartValueTransformer]? {
[
CodingKeys.name <--- FastTransformer<String, String>(fromJSON: { json in
"abc"
}, toJSON: { object in
"123"
}),
CodingKeys.subModel <--- FastTransformer<TestEnum, String>(fromJSON: { json in
TestEnum.man
}, toJSON: { object in
object?.rawValue
}),
]
}
3. 属性包装器
通过自定义属性包装器,赋予模型属性更强大的编解码行为和运行时特性,如类型兼容、键忽略、值扁平化、颜色转换和发布订阅等。这些包装器大大简化了手动处理 Codable 限制的工作,并提升了模型的表达力与灵活性。
| 包装器名 | 功能简述 |
|---|---|
@SmartAny |
支持 Any 类型的编码和解码,包括 [Any] 和 [String: Any]。 |
@IgnoredKey |
忽略属性的编解码,等效于不声明在 CodingKeys 中。 |
@SmartFlat |
将子对象的字段“扁平合并”到当前结构体的字段中进行解码/编码。 |
@SmartHexColor |
支持将十六进制字符串自动转换为颜色对象,如 UIColor / NSColor。 |
@SmartPublished |
为 @Published 属性自动生成支持 Codable 的 getter/setter 逻辑。 |
3.2 @IgnoredKey
如果需要忽略属性解析,可以重写CodingKeys或使用@IgnoredKey:
struct Model: SmartCodable {
@IgnoredKey
var name: String = ""
}
let dict: [String: Any] = [
"name": "Mccc"
]
let model = Model.deserialize(from: dict)
print(model)
// 输出: Model(name: "")
3.3 @SmartFlat
将结构体属性的解码/编码“扁平化处理”,即:在解析当前对象时,自动将其自身字段合并赋值给被包装的子对象。
struct Model: SmartCodable {
var name: String = ""
var age: Int = 0
@SmartFlat
var model: FlatModel?
}
struct FlatModel: SmartCodable {
var name: String = ""
var age: Int = 0
}
let dict: [String: Any] = [
"name": "Mccc",
"age": 18,
]
let model = Model.deserialize(from: dict)
print(model)
// 输出: Model(name: "Mccc", age: 18, model: FlatModel(name: "Mccc", age: 18))
3.4 @SmartHexColor
struct Model: SmartCodable {
@SmartHexColor
var color: UIColor?
}
let dict: [String: Any] = [
"color": "7DA5E3"
]
let model = Model.deserialize(from: dict)
print(model)
// 输出: Model(color: UIExtendedSRGBColorSpace 0.490196 0.647059 0.890196 1)
3.5 @SmartPublished
class PublishedModel: ObservableObject, SmartCodable {
required init() {}
@SmartPublished
var name: ABC?
}
struct ABC: SmartCodable {
var a: String = ""
}
if let model = PublishedModel.deserialize(from: dict) {
// 正确访问name属性的Publisher
model.$name
.sink { newName in
print("name属性发生变化,新值为: \(newName)")
}
.store(in: &cancellables)
}
4. 支持继承
该功能由于使用了 Swift Macro,需要使用 Swift 5.9+,对应的 iOS 13+,因此只在SmartCodable的5.0+版本中支持。
如需要在更低版本使用继承,请查看: 低版本中的继承
如果你需要继承,请使用 @SmartSubclass 标注为子类。
4.1 基础使用
class BaseModel: SmartCodable {
var name: String = ""
required init() { }
}
@SmartSubclass
class StudentModel: BaseModel {
var age: Int?
}
4.2 父类实现协议方法
class BaseModel: SmartCodable {
var name: String = ""
required init() { }
static func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.name <--- "stu_name" ]
}
}
@SmartSubclass
class StudentModel: BaseModel {
var age: Int?
}
4.3 子类实现协议方法
直接实现即可,不需要 override 修饰。
class BaseModel: SmartCodable {
var name: String = ""
required init() { }
class func mappingForKey() -> [SmartKeyTransformer]? {
retrun nil
}
}
@SmartSubclass
class StudentModel: BaseModel {
var age: Int?
override static func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.age <--- "stu_age" ]
}
}
@SmartSubclass
class StudentModel: BaseModel {
var age: Int?
override static func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.age <--- "stu_age" ]
}
}</pre>
4.4 父子类同时实现协议方法
需要注意几点:
父类的类协议方法需要使用
class修饰。子类的类协议方法需要获取父类的实现。
class BaseModel: SmartCodable {
var name: String = ""
required init() { }
class func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.name <--- "stu_name" ]
}
}
@SmartSubclass
class StudentModel: BaseModel {
var age: Int?
override static func mappingForKey() -> [SmartKeyTransformer]? {
let trans = [ CodingKeys.age <--- "stu_age" ]
if let superTrans = super.mappingForKey() {
return trans + superTrans
} else {
return trans
}
}
}
5. 特殊支持
5.1 支持枚举
要使枚举可转换,必须遵循SmartCaseDefaultable协议:
struct Student: SmartCodable {
var name: String = ""
var sex: Sex = .man
enum Sex: String, SmartCaseDefaultable {
case man = "man"
case woman = "woman"
}
}
let model = Student.deserialize(from: json)
要支持 关联值枚举解码 使枚举遵循SmartAssociatedEnumerable,重写mappingForValue方法接管解码过程:
struct Model: SmartCodable {
var sex: Sex = .man
static func mappingForValue() -> [SmartValueTransformer]? {
[
CodingKeys.sex <--- RelationEnumTranformer()
]
}
}
enum Sex: SmartAssociatedEnumerable {
case man
case women
case other(String)
}
struct RelationEnumTranformer: ValueTransformable {
typealias Object = Sex
typealias JSON = String
func transformToJSON(_ value: Sex?) -> String? {
// 自定义处理
}
func transformFromJSON(_ value: Any?) -> Sex? {
// 自定义处理
}
}
5.2 字符串JSON解析
SmartCodable在解码时自动处理字符串化的JSON值,无缝转换为嵌套模型对象或数组,同时保持所有键映射规则:
自动解析:检测并解码字符串化JSON(
"{\"key\":value}")为适当对象/数组递归映射:对解析的嵌套结构应用
mappingForKey()规则类型推断:根据属性类型确定解析策略(对象/数组)
struct Model: SmartCodable {
var hobby: Hobby?
var hobbys: [Hobby]?
}
struct Hobby: SmartCodable {
var name: String = ""
}
let dict: [String: Any] = [
"hobby": "{\"name\":\"sleep1\"}",
"hobbys": "[{\"name\":\"sleep2\"}]",
]
guard let model = Model.deserialize(from: dict) else { return }
5.3 兼容性
当属性解析失败时,SmartCodable会对抛出的异常进行兼容处理,确保整个解析过程不会中断:
let dict = [
"number1": "123",
"number2": "Mccc",
"number3": "Mccc"
]
struct Model: SmartCodable {
var number1: Int?
var number2: Int?
var number3: Int = 1
}
// 解码结果
// Model(number1: 123, number2: nil, number3: 1)
类型转换兼容性
当数据类型不匹配时(引发.typeMismatch错误),SmartCodable会尝试将String类型数据转换为所需的Int类型。
默认值填充兼容性
当类型转换失败时,会获取当前解析属性的初始化值进行填充。
5.4 更新现有模型
可适应任何数据结构,包括嵌套数组结构:
struct Model: SmartCodable {
var name: String = ""
var age: Int = 0
}
var dic1: [String : Any] = [
"name": "mccc",
"age": 10
]
let dic2: [String : Any] = [
"age": 200
]
guard var model = Model.deserialize(from: dic1) else { return }
SmartUpdater.update(&model, from: dic2)
// 现在: model是 ["name": mccc, "age": 200].
5.5 解析超大体积数据
当解析超大体积数据时,尽量避免解析异常的兼容处理,例如:属性中声明了多个属性,且声明的属性类型不匹配。
不需要解析的属性不要使用@IgnoredKey,而是重写CodingKeys来忽略不需要解析的属性。
这样可以大幅提高解析效率。
哨兵系统(Sentinel)
SmartCodable集成了Smart Sentinel,它会监听整个解析过程。解析完成后,会显示格式化的日志信息。
这些信息仅作为辅助信息帮助您发现和纠正问题,并不意味着解析失败。
================================ [Smart Sentinel] ================================
Array<SomeModel> 👈🏻 👀
╆━ Index 0
┆┄ a: Expected to decode 'Int' but found ‘String’ instead.
┆┄ b: Expected to decode 'Int' but found ’Array‘ instead.
┆┄ c: No value associated with key.
╆━ sub: SubModel
┆┄ sub_a: No value associated with key.
┆┄ sub_b: No value associated with key.
┆┄ sub_c: No value associated with key.
╆━ sub2s: [SubTwoModel]
╆━ Index 0
┆┄ sub2_a: No value associated with key.
┆┄ sub2_b: No value associated with key.
┆┄ sub2_c: No value associated with key.
╆━ Index 1
┆┄ sub2_a: Expected to decode 'Int' but found ’Array‘ instead.
╆━ Index 1
┆┄ a: No value associated with key.
┆┄ b: Expected to decode 'Int' but found ‘String’ instead.
┆┄ c: Expected to decode 'Int' but found ’Array‘ instead.
╆━ sub: SubModel
┆┄ sub_a: Expected to decode 'Int' but found ‘String’ instead.
╆━ sub2s: [SubTwoModel]
╆━ Index 0
┆┄ sub2_a: Expected to decode 'Int' but found ‘String’ instead.
╆━ Index 1
┆┄ sub2_a: Expected to decode 'Int' but found 'null' instead.
====================================================================================
如需使用,请开启:
SmartSentinel.debugMode = .verbose
public enum Level: Int {
case none
case verbose
case alert
}
如需将日志上传到服务器:
SmartSentinel.onLogGenerated { logs in }