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 協(xié)議,類需要實現(xiàn)空初始化器:
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. 多格式輸入支持
| 輸入類型 | 使用示例 | 內部轉換 |
|---|---|---|
| 字典/數(shù)組 | Model.deserialize(from: dict或arr) |
直接處理原生集合 |
| JSON字符串 | Model.deserialize(from: jsonString) |
通過UTF-8轉換為Data
|
| 二進制數(shù)據(jù) | Model.deserialize(from: data) |
直接處理 |
2. 深度路徑解析(designatedPath)
// JSON結構:
{
"data": {
"user": {
"info": { ...目標內容... }
}
}
}
// 訪問嵌套數(shù)據(jù):
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日期策略 |
| 數(shù)據(jù)解碼 | .base64 |
二進制數(shù)據(jù)處理 |
| 浮點數(shù)解碼 |
.convertToString, .throw
|
NaN/∞處理 |
?? 重要: 每種策略類型只允許一個選項(重復時最后一個生效)
2.2 解碼成功后調用的后處理回調
struct Model: SmartCodable {
var name: String = ""
mutating func didFinishMapping() {
name = "我是 \(name)"
}
}
2.3 鍵轉換
定義解碼時的鍵映射轉換,優(yōu)先使用第一個有效映射:
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))
]
}
}
如果需要額外解析規(guī)則,可以自己實現(xiàn)Transformer。遵循ValueTransformable協(xié)議實現(xiàn)要求:
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. 屬性包裝器
通過自定義屬性包裝器,賦予模型屬性更強大的編解碼行為和運行時特性,如類型兼容、鍵忽略、值扁平化、顏色轉換和發(fā)布訂閱等。這些包裝器大大簡化了手動處理 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屬性發(fā)生變化,新值為: \(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 父類實現(xiàn)協(xié)議方法
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 子類實現(xiàn)協(xié)議方法
直接實現(xiàn)即可,不需要 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 父子類同時實現(xiàn)協(xié)議方法
需要注意幾點:
父類的類協(xié)議方法需要使用
class修飾。子類的類協(xié)議方法需要獲取父類的實現(xiàn)。
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協(xié)議:
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)
要支持 關聯(lián)值枚舉解碼 使枚舉遵循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值,無縫轉換為嵌套模型對象或數(shù)組,同時保持所有鍵映射規(guī)則:
自動解析:檢測并解碼字符串化JSON(
"{\"key\":value}")為適當對象/數(shù)組遞歸映射:對解析的嵌套結構應用
mappingForKey()規(guī)則類型推斷:根據(jù)屬性類型確定解析策略(對象/數(shù)組)
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)
類型轉換兼容性
當數(shù)據(jù)類型不匹配時(引發(fā).typeMismatch錯誤),SmartCodable會嘗試將String類型數(shù)據(jù)轉換為所需的Int類型。
默認值填充兼容性
當類型轉換失敗時,會獲取當前解析屬性的初始化值進行填充。
5.4 更新現(xiàn)有模型
可適應任何數(shù)據(jù)結構,包括嵌套數(shù)組結構:
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)
// 現(xiàn)在: model是 ["name": mccc, "age": 200].
5.5 解析超大體積數(shù)據(jù)
當解析超大體積數(shù)據(jù)時,盡量避免解析異常的兼容處理,例如:屬性中聲明了多個屬性,且聲明的屬性類型不匹配。
不需要解析的屬性不要使用@IgnoredKey,而是重寫CodingKeys來忽略不需要解析的屬性。
這樣可以大幅提高解析效率。
哨兵系統(tǒng)(Sentinel)
SmartCodable集成了Smart Sentinel,它會監(jiān)聽整個解析過程。解析完成后,會顯示格式化的日志信息。
這些信息僅作為輔助信息幫助您發(fā)現(xiàn)和糾正問題,并不意味著解析失敗。
================================ [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 }