Skip to content

huluo666/NovaCodable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

NovaCodable

NovaCodable 是一款基于 Swift Macro(编译期代码生成)的高性能、轻量级、零反射的 JSON 序列化与反序列化基建库。它通过在编译期静态生成平铺的官方 Codable 代码,彻底消灭了运行时反射带来的性能开销,同时针对 Swift 编译器进行了 AST 树深度优化,大幅提升了大型项目中的类型推断与编译速度。

本文件旨在提供一份标准的通用开发规范,引导业务层正确、高效地编写数据模型。


一、 核心特性与设计哲学

  1. 零侵入与弱耦合:业务模型无需继承特定基类,通过 @Codable 装饰即可自动获得完整的原生产能。
  2. 静态平铺提速:底层摒弃了深层泛型嵌套、去除了 ??(空合运算符)的类型推断耗时,编译速度无限逼近纯手写代码。
  3. 智能类型容错:内置 decodeSmart 引擎,支持常用基础类型的无感自动转换(例如:后端返回 Int/Double,模型定义为 String 时,会自动转为对应字符串,绝不因类型不匹配导致整个模型白屏或崩溃)。
  4. 安全防崩溃:重写可选值编码逻辑,当值为 nil 时直接剔除 Key,彻底断绝包含 <null>NSNull)的脏字典写入 UserDefaults 时引发的线上 Crash。

二、 基础模型示例(用户信息)

以下以日常业务最频繁的用户信息模型为例,全面展示标准字段、字面量默认值、可选型、自定义映射、枚举兜底以及模型嵌套的推荐写法。

1. 完整代码示例

Swift

import Foundation
import NovaCodable

// ==========================================
// 辅助模型 1:安全枚举兜底
// ==========================================
@CodableEnum(fallback: "unknown")
public enum UserGender: String {
    case male = "MALE"
    case female = "FEMALE"
    case unknown = "UNKNOWN" // 当后端返回未知的状态(如 "OTHER")时,安全兜底为此处
}

// ==========================================
// 辅助模型 2:被嵌套的勋章模型
// ==========================================
@Codable
public class UserMedalDTO {
    public var medalId: String = ""
    public var medalName: String = ""
    public var iconUrl: String = ""
}

// ==========================================
// 主模型:用户信息响应体
// ==========================================
@Codable
public class UserInfoResponse {
    
    /// 用户唯一标识符 (字符串类型)
    public var id: String = ""
    
    /// 用户业务Code (支持智能容错:若后端返回数字 20001630,会自动转为 "20001630")
    public var code: String = ""
    
    /// 用户昵称 (字面量默认值,若JSON中缺失该字段,则保留此处默认值)
    public var nickname: String = "未命名用户"
    
    /// 用户头像 (可选型,允许为 nil)
    public var avatar: String? = nil
    
    /// 地区码 (如 "TR")
    public var areaCode: String = ""
    
    /// 国家码 (自定义Key映射:将响应体中的 "country_code" 映射到该属性)
    @SerializedName("country_code")
    public var countryCode: String = ""
    
    /// 用户性别 (内置枚举类型,安全容错)
    public var gender: UserGender = .unknown
    
    /// 用户勋章列表 (模型嵌套 + 可选型数组)
    public var medalsV2: [UserMedalDTO]? = nil
    
    /// 隐藏的敏感字段/本地临时字段 (不参与任何 JSON 解析与编码)
    @CodableIgnore
    public var localCacheTimestamp: Int = 0
    public var thumbnails: [[String: AnyCodable]]?
}

AnyCodable使用https://github.com/Flight-School/AnyCodable

2. 写法要点解析

  • 字面量默认值(Default Values):强烈建议为所有必选基础字段(如 String, Int, Double, Bool)赋予初始字面量。当服务器返回的 JSON 缺失某些字段时,NovaCodable 会保留本地初始值,确保业务逻辑拿到的是安全可控的数据。
  • 可选型(Optional ?:只有确定该字段允许为 nil 且业务有针对 nil 的专门判断(如控制某些 UI 组件的显示与隐藏)时,才建议使用 ?
  • 智能转换验证:上述例子中,即使服务器下发的 code 是整型 12345,业务层无需修改模型,底层重载的 decodeSmart 会在内存组装时默默将其安全的格式化为 "12345"
  • 属性忽略(@CodableIgnore:常用于页面打点、本地计时器、或是状态暂存字段,带上此标签后,编码和解码都会完全无视它。

三、 高级通用场景进阶写法

1. 模型继承场景(@CodableSubclass)

当业务模型存在复杂的父子继承结构时,基类子类必须分工明确。子类不再使用 @Codable,而是显式通过 @CodableSubclass 声明,宏会自动补齐子类所必须的 super.init()override encode(to:) 派发。

Swift

/// 1. 基础事件基类
@Codable
public class LMRoomBaseEvent {
    public var eventId: String = ""
    public var createTime: Int = 0
    
    public init() {} // 显式提供给子类 super 调用
}

/// 2. 具体的礼物事件子类
@CodableSubclass
public class LMRoomGiftEvent: LMRoomBaseEvent {
    public var giftId: String = ""
    public var giftName: String = ""
    public var giftNum: Int = 1
    
    public override init() {
        super.init()
    }
}

2. 局部数据变形(@Transform)

当后端下发的数据格式与客户端纯净的业务模型内存类型不一致,且该转换极其复杂或影响列表滑动性能时,推荐使用轻量级转换器将计算提前。

Swift

// 1. 实现通用转换器协议(全局编译一次,不增加宏开销)
public struct StringToIntArrayTransformer: NovaTransformer {
    public typealias JSONType = String
    public typealias ModelType = [Int]
    
    // 解码:将后台返回的 "101,102,103" 在后台线程一次性切好存在内存
    public static func transform(from json: String) -> [Int] {
        return json.components(separatedBy: ",").compactMap { Int($0) }
    }
    
    // 编码:反向回退成字符串发给后台
    public static func transform(to model: [Int]) -> String {
        return model.map { "\($0)" }.joined(separator: ",")
    }
}

// 2. 业务使用
@Codable
public class LMRoomPermissionModel {
    /// 房间管理员ID列表(在解析瞬间完成转换,UI层直接无感读取高效数组,杜绝掉帧)
    @Transform(StringToIntArrayTransformer.self)
    public var administratorIds: [Int] = []
}

3. 逃生舱:关闭全属性构造器(memberwiseInit: false

默认情况下,宏会在底层为没有自定义构造器的类自动补充一个全字段入参的 init(id:...) 以及空的 init()

若模型内有极其复杂的自定义初始化逻辑,或出于对大型响应体模型极限压缩代码量、缩短编译时间的目的(纯响应模型在客户端不需要手动 new 创建对象),可以手动关闭它。

Swift

@Codable(memberwiseInit: false) // 👈 明确告知宏:关闭自动 init 生成,权限完全交还手写
public class LMGiftBackpack {
    public var categoryId: String = ""
    public var categoryName: String = ""
    
    // 手动补充精简的空构造器,供外部或子类调用
    public init() {} 
}

四、 核心数据流网关适配(老项目无缝适配指南)

为了保证迁移过程中老代码的绝对安全,不要直接大范围替换业务层的调用,可以通过框架层暴露的全局扩展网关,安全透明地接管项目的输入与输出。

1. 字典/字符串与模型互转(无缝兼容 HandyJSON 习惯)

Swift

// 转换示例:
let dict: [String: Any] = ["id": "63801", "code": 20001630]

// 🛠 解码 (deserialize)
if let userInfo = UserInfoResponse.deserialize(from: dict) {
    print("解析成功,昵称:\(userInfo.nickname)")
}

// 🛠 编码 (toJSON)
if let cleanDict = userInfo.toJSON() {
    // 此时 cleanDict 已经过底层递归洗白(removingNSNulls),不包含任何 NSNull 
    UserDefaults.standard.set(cleanDict, forKey: "LM_currentUser") // 💯 绝对不崩溃
}

2. 规范小结

业务研发进行新模型编写或旧模型重构时,请严格参考上述 UserInfoResponse 的标准进行声明,善用 @CodableEnum 替换原生的不稳定枚举。这能有效保障线上崩溃率维持在极低的水准,同时保障 App 的快速迭代构建。

Python

content = """# NovaCodable

NovaCodable 是一款基于 Swift Macro(编译期代码生成)的高性能、轻量级、零反射的 JSON 序列化与反序列化基建库。
它通过在编译期静态生成平铺的官方 `Codable` 代码,彻底消灭了运行时反射带来的性能开销。同时针对 Swift 编译器进行了 AST 树深度优化,大幅提升了大型项目中的类型推断与编译速度。

本指南旨在帮助开发者**从零开始,快速上手并无缝迁移**现有业务模型。

---

## 一、 核心优势

1. **极致编译速度**:底层摒弃深层泛型嵌套,编译速度无限逼近纯手写原生代码,告别宏展开卡顿。
2. **零侵入与弱耦合**:无需继承特定的基类,只需打上 `@Codable` 标签即可自动获得解析能力。
3. **智能类型容错**:内置强悍的容错引擎,支持基础类型无感转换(如:后端下发 `Int` 123,模型定义为 `String` 时,自动转为 `"123"`)。
4. **安全防崩溃**:重写可选值编码逻辑,自动剔除 `nil` 值的 Key,彻底告别旧字典写入 `UserDefaults` 时由 `<null>` 引发的线上 Crash。

---

## 二、 🚀 快速迁移指南(1分钟上手)

如果你正在从 HandyJSON、SmartCodable 或其他第三方库迁移,只需遵循以下三个步骤。

### Step 1: 基础模型替换
去掉原有的继承或协议,直接在模型顶部添加 `@Codable` 宏。强烈建议为必选字段赋予**初始字面量默认值**。

Code output

README.md created successfully

```swift
import NovaCodable

@Codable
public class UserInfoResponse {
    /// 正常字段:推荐赋予默认值
    public var id: String = ""
    public var nickname: String = "未命名用户"
    
    /// 可选字段:允许为 nil 时使用 `?`
    public var avatar: String? = nil
}

Step 2: 处理自定义 Key 映射

如果后端的 JSON 字段名(如 country_code)与客户端变量名(如 countryCode)不一致,使用 @SerializedName

Swift

@Codable
public class UserInfoResponse {
    @SerializedName("country_code")
    public var countryCode: String = ""
}

Step 3: 忽略本地无关字段

如果某些字段仅用于本地缓存或页面状态,不需要参与网络 JSON 的解析与生成,使用 @CodableIgnore

Swift

@Codable
public class UserInfoResponse {
    @CodableIgnore
    public var localCacheTimestamp: Int = 0
}

三、 高阶场景与进阶用法

1. 安全枚举兜底 (@CodableEnum & @CodableDefault)

痛点:后端突然新增枚举类型,导致前端原生 Codable 解析失败,整个页面白屏崩溃。

解法:使用 @CodableEnum 并用 @CodableDefault 指定一个未知的兜底项。

Swift

@CodableEnum
public enum UserGender: String {
    case male = "MALE"
    case female = "FEMALE"
    
    @CodableDefault
    case unknown = "UNKNOWN" // 🛡 当后端返回未知的 "OTHER" 等状态时,安全回退到此处
}

2. 模型继承场景 (@CodableSubclass)

当业务模型存在复杂的父子继承结构时,基类使用 @Codable子类必须使用 @CodableSubclass。宏会自动补齐子类所需的 super.init() 调用。

Swift

/// 1. 基础事件基类
@Codable
public class LMRoomBaseEvent {
    public var eventId: String = ""
    public var createTime: Int = 0
    
    public init() {} // 显式提供给子类 super 调用
}

/// 2. 具体的礼物事件子类
@CodableSubclass
public class LMRoomGiftEvent: LMRoomBaseEvent {
    public var giftId: String = ""
    public var giftName: String = ""
    
    // 宏会自动生成全属性构造器并安全调用 super.init()
}

3. 逃生舱:手动接管初始化 (memberwiseInit: false)

默认情况下,宏会自动在底层补充一个全字段入参的 init(id:...) 以及空的 init()

如果你的模型有极其特殊的自定义初始化逻辑,或者想极致压缩代码量(如纯网络响应模型,无需手动实例化),可以手动关闭它。

Swift

@Codable(memberwiseInit: false) // 👈 明确告知宏:关闭自动 init 生成,权限完全交还手写
public class LMGiftBackpack {
    public var categoryId: String = ""
    public var categoryName: String = ""
    
    // 手动补充精简的空构造器,供外部或子类调用
    public init() {} 
}

About

Light as air, fast as wind. Evolving JSON serialization with modern Swift Codable.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages