使用 Swift macros 对 Codable 进行扩展, 以声明式注解让序列化变得更简单!
@Codable
@SnakeCase
struct User {
@CodingKey("user_name")
var name: String
@KebabCase
@DateCoding(.iso8601)
var birthDate: Date
@CodingKey("location.city")
var city: String
@CustomCoding<Double>(
decode: { return try $0.value(forKeys: "height_in_meters") * 100.0 },
encode: { try $0.set($1 / 100.0, forKey: "height_in_meters") }
)
var height: Double
}ReerCodable 框架提供了一系列自定义宏,用于生成动态的 Codable 实现。该框架的核心是 @Codable() 宏,它可以在其他宏提供的数据标记下生成具体的实现(@Cdable 宏, 展开其他宏是没有响应的)
框架使用了 Swift Testing 进行了全覆盖测试, 主要包含以下 feature:
- 通过
@CodingKey("key")为每个属性声明自定义的CodingKey值, 无需编写所有的CodingKey值. - 支持通过字符串表达嵌套的
CodingKey, 如@CodingKey("nested.key") - 允许使用多个
CodingKey来进行 Decode, 如@CodingKey("key1", "key2") - 支持使用
@SnakeCase,KebabCase等来标记类型或属性来方便地实现命名转换 - 通过使用
@CodingContainer自定义 Coding 时的嵌套容器 - 支持 Encode 时指定的
CodingKey, 如EncodingKey("encode_key") - 允许解码失败时使用默认值, 从而避免
keyNotFound错误发生 - 允许使用
@CodingIgnored、@EncodingIgnored、@DecodingIgnored在编解码过程中按需忽略特定属性 - 支持使用
@Base64Coding自动对 base64 字符串和Data[UInt8]类型进行转换 - 在 Decode
Array,Dictionary,Set时, 通过@CompactDecoding可以忽略null值, 而不是抛出错误 - 支持通过
@DateCoding实现对Date的各种编解码 - 支持通过
@CustomCoding实现自定义编解码逻辑 - 通过使用
@InheritedCodable对子类有更好的支持 - 对各类
enum提供简单而丰富的编解码能力 - 支持通过
ReerCodableDelegate来编解码生命周期, 如didDecode,willEncode - 提供扩展, 支持使用 JSON String,
Dictionary,Array直接作为参数进行编解码 - 通过
@FlexibleType支持Bool,String,Double,Int,CGFloat等基本数据类型互相转换 - 支持 BigInt
Int128,UInt128(限 macOS 15+, iOS 13+) - 支持通过
AnyCodable来实现对Any的编解码, 如var dict = [String: AnyCodable] - 使用
@FlatCoding在编解码时将嵌套属性“拍平”到父级结构 - 支持通过
@DefaultInstance生成一个static let default: Model实例,Model.default - 支持通过
@Copyable生成copy()方法, 并且支持部分属性值的 update - 自动生成默认实例:使用
@DefaultInstance自动创建类型的默认实例, 可通过 Model.default 访问 - 灵活的复制与更新:
@Copyable宏会生成一个 copy() 方法, 支持在一次调用中实现完整复制或选择性地更新属性值 - 支持单独使用
@Decodable或@Encodable - 完全兼容继承自
NSObject的类
XCode 16.0+
iOS 13.0+, macOS 10.15+, tvOS 13.0+, visionOS 1.0+, watchOS 6.0+
Swift 5.10+
swift-syntax 600.0.0+
Swift Package Manager
你可以使用 The Swift Package Manager 来安装 ReerCodable,请在你的 Package.swift 文件中添加正确的描述:
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
targets: [],
dependencies: [
.package(url: "https://github.com/reers/ReerCodable.git", from: "1.7.2")
]
)
接下来,将 ReerCodable 添加到您的 targets 依赖项中,如下所示:
.product(name: "ReerCodable", package: "ReerCodable"),然后运行 swift package update。
CocoaPods
由于 CocoaPods 不直接支持 Swift Macro,宏实现会编译为预构建的二进制插件,ReerCodable 在构建时自动下载。有以下几种接入场景:
如果只在主 App 中使用 @Codable(不在其他 Pod 组件中使用),只需在 Podfile 中添加:
pod 'ReerCodable', '1.7.2'
如果你的 Pod 需要使用 @Codable 等 ReerCodable 宏,必须在你的 podspec 中添加 pod_target_xcconfig 来加载宏插件二进制。这是因为 CocoaPods 不会将依赖 Pod 的构建配置自动传递给依赖方:
Pod::Spec.new do |s|
s.name = 'YourPod'
s.dependency 'ReerCodable', '1.7.2'
# 必须添加:加载 ReerCodable 宏插件
s.pod_target_xcconfig = {
'OTHER_SWIFT_FLAGS' => '-Xfrontend -load-plugin-executable -Xfrontend ${PODS_BUILD_DIR}/ReerCodable/MacroPlugin/ReerCodableMacros#ReerCodableMacros'
}
end
如果你有多个 Pod 依赖 ReerCodable,可以在 Podfile 中添加如下 post_install hook,自动为所有依赖 ReerCodable 的 Pod 注入宏插件配置:
post_install do |installer|
macro_flag = '-Xfrontend -load-plugin-executable -Xfrontend ${PODS_BUILD_DIR}/ReerCodable/MacroPlugin/ReerCodableMacros#ReerCodableMacros'
reer_codable_dependents = Set.new
installer.pod_targets.each do |pod_target|
next if pod_target.name == 'ReerCodable'
has_dep = pod_target.dependent_targets.any? { |dep| dep.name == 'ReerCodable' }
reer_codable_dependents.add(pod_target.name) if has_dep
end
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if reer_codable_dependents.include?(target.name)
flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)'
unless flags.include?('ReerCodableMacros')
config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} #{macro_flag}"
end
end
end
end
end
rsync 权限错误,需关闭用户脚本沙盒:
在工程的 Build Settings 中搜索 User Script Sandboxing,将 ENABLE_USER_SCRIPT_SANDBOXING 设为 No。这可解决 Xcode 严格沙盒限制导致的 CocoaPods 脚本执行失败问题。
ReerCodable 通过声明式注解大大简化了 Swift 的序列化过程。以下是各个特性的详细使用示例:
通过 @CodingKey 可以为属性指定自定义 key,无需手动编写 CodingKeys 枚举:
| ReerCodable | Codable |
|---|---|
@Codable
struct User {
@CodingKey("user_name")
var name: String
@CodingKey("user_age")
var age: Int
var height: Double
} |
struct User: Codable {
var name: String
var age: Int
var height: Double
enum CodingKeys: String, CodingKey {
case name = "user_name"
case age = "user_age"
case height
}
} |
支持通过点语法表示嵌套的 key path:
@Codable
struct User {
@CodingKey("other_info.weight")
var weight: Double
@CodingKey("location.city")
var city: String
}可以指定多个 key (包括 nested key)用于解码,系统会按顺序尝试解码直到成功:
@Codable
struct User {
@CodingKey("name", "username", "nick_name", "user_info.name")
var name: String
}支持多种命名风格转换,可以应用在类型或单个属性上:
@Codable
@SnakeCase
struct Person {
var firstName: String // 从 "first_name" 解码, 或编码为 "first_name"
@KebabCase
var lastName: String // 从 "last-name" 解码, 或编码为 "last-name"
}使用 @CodingContainer 自定义编解码时的容器路径, 通常用于JSON嵌套较多, 但 model 声明 想直接 match 子层级结构:
| ReerCodable | JSON |
|---|---|
@Codable
@CodingContainer("data.info")
struct UserInfo {
var name: String
var age: Int
} |
{
"code": 0,
"data": {
"info": {
"name": "phoenix",
"age": 33
}
}
} |
可以为编码过程指定不同的键名, 由于 @CodingKey 可能有多个参数, 再加上可以使用 @SnakeCase, KebabCase 等, 解码可能使用多个 key, 那编码时会采用第一个 key, 也可以通过 @EncodingKey 来指定 key
@Codable
struct User {
@CodingKey("user_name") // 解码使用 "user_name", "name"
@EncodingKey("name") // 编码使用 "name"
var name: String
}解码失败时可以使用默认值, 原生 Codable 针对非 Optional 属性, 会在没有解析到正确值是抛出异常, 即使已经设置了初始值, 或者即使是 Optional 类型的枚举
@Codable
struct User {
var age: Int = 33
var name: String = "phoenix"
// 若 JSON 中 gender 字段不是 `male` 或 `female`, 原生 Codable 会抛出异常, ReerCodable 不会, 会设置其为 nil, 如 {"gender": "other"}, 可能出现在客户端定义了枚举, 但服务端新增了字段的业务场景
var gender: Gender?
}
@Codable
enum Gender: String {
case male, female
}如果需要更精细地控制默认值, 可以使用 @DecodingDefault, @EncodingDefault, @CodingDefault:
@Decodable
struct Flags {
@DecodingDefault(false)
var isEnabled: Bool
}
@Encodable
struct Payload {
@EncodingDefault("anonymous")
var nickname: String?
}
@Codable
struct Preferences {
@CodingDefault([String]())
var tags: [String]?
}@DecodingDefault 会在解码失败或缺失时使用提供的表达式, @EncodingDefault 会在编码 nil 可选值时使用该表达式, 而 @CodingDefault 同时具备前两者的行为。
使用 @CodingIgnored 可以同时在编码和解码时忽略属性, @EncodingIgnored 只在编码时忽略, @DecodingIgnored 只在解码时忽略. 当属性会被解码侧忽略时, 对于非 Optional 属性要有一个默认值才能满足 Swift 初始化的要求, ReerCodable 对基本数据类型和集合类型会自动生成默认值, 如果是其他自定义类型, 则需由用户提供默认值.
@Codable
struct User {
var name: String
@CodingIgnored
var transient: Set<String>
@EncodingIgnored
var serverToken: String
@DecodingIgnored
var localDraft: String = "draft"
}自动处理 base64 字符串与 Data, [UInt8] 类型的转换:
@Codable
struct User {
@Base64Coding
var avatar: Data
@Base64Coding
var voice: [UInt8]
}使用 @CompactDecoding 在解码数组时自动过滤 null 值, 与 compactMap 是相同的意思:
@Codable
struct User {
@CompactDecoding
var tags: [String] // ["a", null, "b"] 将被解码为 ["a", "b"]
}同时, Dictionary 和 Set 也支持使用 @CompactDecoding 来优化
支持多种日期格式的编解码:
| ReerCodable | JSON |
|---|---|
@Codable
class DateModel {
@DateCoding(.timeIntervalSince2001)
var date1: Date
@DateCoding(.timeIntervalSince1970)
var date2: Date
@DateCoding(.secondsSince1970)
var date3: Date
@DateCoding(.millisecondsSince1970)
var date4: Date
@DateCoding(.iso8601)
var date5: Date
@DateCoding(.iso8601)
var date6: Date
@DateCoding(.formatted(Self.formatter))
var date7: Date
static let formatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy'Year'MM'-Month'dd'*Day 'HH'h'mm'm'ss's'"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
return dateFormatter
}()
} |
{
"date1": 1431585275,
"date2": 1731585275.944,
"date3": 1731585275,
"date4": 1731585275944,
"date5": "2024-12-10T00:00:00Z",
"date6": "2025-04-17T00:00:00.000Z",
"date7": "2024Year12-Month10*Day 00h00m00s"
} |
如需更精细地控制 ISO8601 编码格式,可使用 .iso8601WithOptions 指定精度和时区:
@Codable
struct Event {
// 默认:秒精度,UTC 时区 -> "2024-01-15T12:00:00Z"
@DateCoding(.iso8601WithOptions())
var createdAt: Date
// 毫秒精度(3位小数) -> "2024-01-15T12:00:00.123Z"
@DateCoding(.iso8601WithOptions(precision: .milliseconds))
var timestamp: Date
// 微秒精度(6位小数) -> "2024-01-15T12:00:00.123456Z"
@DateCoding(.iso8601WithOptions(precision: .microseconds))
var preciseTime: Date
// 本地时区 -> "2024-01-15T20:00:00+08:00"
@DateCoding(.iso8601WithOptions(timeZone: .local))
var localTime: Date
// 固定时区偏移 -> "2024-01-15T20:00:00.123+08:00"
@DateCoding(.iso8601WithOptions(precision: .milliseconds, timeZone: .offsetHours(8)))
var beijingTime: Date
// 通过标识符指定时区 -> "2024-01-15T21:00:00+09:00"
@DateCoding(.iso8601WithOptions(timeZone: .identifier("Asia/Tokyo")))
var tokyoTime: Date
}可用选项:
| DatePrecision | 说明 | 示例 |
|---|---|---|
.seconds |
无小数秒(默认) | "12:00:00" |
.milliseconds |
3位小数 | "12:00:00.123" |
.microseconds |
6位小数 | "12:00:00.123456" |
| TimeZoneStyle | 说明 | 示例 |
|---|---|---|
.utc |
UTC 时区(默认) | "Z" |
.local |
设备本地时区 | "+08:00" |
.offsetHours(Int) |
固定小时偏移(-12 到 +14) | "+08:00" |
.offsetSeconds(Int) |
固定秒数偏移 | "+05:30" |
.identifier(String) |
时区标识符 | "Asia/Tokyo" → "+09:00" |
注意: 当时区偏移为 0 时,输出遵循 RFC 3339 标准使用
"Z"后缀(与 Go、Apple、JavaScript、Java 行为一致)。
通过 @CustomCoding 实现自定义的编解码逻辑. 自定义编解码有两种方式:
- 通过闭包, 以
decoder: any Decoder,encoder: any Encoder为参数来实现自定义逻辑:
@Codable
struct User {
@CustomCoding<Double>(
decode: { return try $0.value(forKeys: "height_in_meters") * 100.0 },
encode: { try $0.set($1 / 100.0, forKey: "height_in_meters") }
)
var heightInCentimeters: Double
}- 通过一个实现
CodingCustomizable协议的自定义类型来实现自定义逻辑:
// 1st 2nd 3rd 4th 5th -> 1 2 3 4 5
struct RankTransformer: CodingCustomizable {
typealias Value = UInt
static func decode(by decoder: any Decoder, keys: [String]) throws -> UInt {
var temp: String = try decoder.value(forKeys: keys)
temp.removeLast(2)
return UInt(temp) ?? 0
}
static func encode(by encoder: any Encoder, key: String, value: Value) throws {
try encoder.set(value, forKey: key)
}
}
@Codable
struct HundredMeterRace {
@CustomCoding(RankTransformer.self)
var rank: UInt
}自定义实现过程中, 框架提供的方法也可以使编解码更加方便:
public extension Decoder {
func value<Value: Decodable>(forKeys keys: String...) throws -> Value {
let container = try container(keyedBy: AnyCodingKey.self)
return try container.decode(type: Value.self, keys: keys)
}
}
public extension Encoder {
func set<Value: Encodable>(_ value: Value, forKey key: String, treatDotAsNested: Bool = true) throws {
var container = container(keyedBy: AnyCodingKey.self)
try container.encode(value: value, key: key, treatDotAsNested: treatDotAsNested)
}
}使用 @InheritedCodable 更好地支持子类的编解码. 原生 Codable 无法解析子类属性, 即使 JSON 中存在该值, 需要手动实现 init(from decoder: any Decoder) throws
@Codable
class Animal {
var name: String
}
@InheritedCodable
class Cat: Animal {
var color: String
}为枚举提供丰富的编解码能力:
- 对基本枚举类型, 以及 RawValue 枚举支持
@Codable
struct User {
let gender: Gender
let rawInt: RawInt
let rawDouble: RawDouble
let rawDouble2: RawDouble2
let rawString: RawString
}
@Codable
enum Gender {
case male, female
}
@Codable
enum RawInt: Int {
case one = 1, two, three, other = 100
}
@Codable
enum RawDouble: Double {
case one, two, three, other = 100.0
}
@Codable
enum RawDouble2: Double {
case one = 1.1, two = 2.2, three = 3.3, other = 4.4
}
@Codable
enum RawString: String {
case one, two, three, other = "helloworld"
}- 支持使用
CodingCase(match: ....)来匹配多个值或 range
@Codable
enum Phone: Codable {
@CodingCase(match: .bool(true), .int(10), .string("iphone"), .intRange(22...30))
case iPhone
@CodingCase(match: .int(12), .string("MI"), .string("xiaomi"), .doubleRange(50...60))
case xiaomi
@CodingCase(match: .bool(false), .string("oppo"), .stringRange("o"..."q"))
case oppo
}-
对于有关联值的枚举, 支持通用
AssociatedValue来匹配关联值, 使用.label()来声明有标签的关联值的匹配逻辑, 使用.index()来声明没有标签的的关联值的匹配逻辑.ReerCodable支持两种JSON 格式的枚举匹配- 第一种是也是原生
Codable支持的, 即枚举值和其关联值是父子级的结构:
@Codable enum Video: Codable { /// { /// "YOUTUBE": { /// "id": "ujOc3a7Hav0", /// "_1": 44.5 /// } /// } @CodingCase(match: .string("youtube"), .string("YOUTUBE")) case youTube /// { /// "vimeo": { /// "ID": "234961067", /// "minutes": 999999 /// } /// } @CodingCase( match: .string("vimeo"), values: [.label("id", keys: "ID", "Id"), .index(2, keys: "minutes")] ) case vimeo(id: String, duration: TimeInterval = 33, Int) /// { /// "tiktok": { /// "url": "https://example.com/video.mp4", /// "tag": "Art" /// } /// } @CodingCase( match: .string("tiktok"), values: [.label("url", keys: "url")] ) case tiktok(url: URL, tag: String?) }
- 第二种是枚举值和其关联值同级或自定义匹配的结构, 使用带有 key path 的 CaseMatcher 进行自定义路径值的匹配
@Codable enum Video1: Codable { /// { /// "type": { /// "middle": "youtube" /// } /// } @CodingCase(match: .string("youtube", at: "type.middle")) case youTube /// { /// "type": "vimeo", /// "ID": "234961067", /// "minutes": 999999 /// } @CodingCase( match: .string("vimeo", at: "type"), values: [.label("id", keys: "ID", "Id"), .index(2, keys: "minutes")] ) case vimeo(id: String, duration: TimeInterval = 33, Int) /// { /// "type": "tiktok", /// "media": "https://example.com/video.mp4", /// "tag": "Art" /// } @CodingCase( match: .string("tiktok", at: "type"), values: [.label("url", keys: "media")] ) case tiktok(url: URL, tag: String?) }
- 第一种是也是原生
-
支持在枚举上使用
@SnakeCase、@PascalCase等命名风格宏, 自动转换 case 名称用于编解码. 解码时取并集(多对一), 编码时使用最高优先级的值
@Codable
@PascalCase
enum Status {
case inProgress // 编码/解码 "InProgress"
case notStarted // 编码/解码 "NotStarted"
}可以在单个 case 上覆盖 enum 级别的风格:
@Codable
@PascalCase
enum Event {
@CodingCase(match: .string("pgview"))
@SnakeCase
case pageView // 解码: ["pgview", "page_view", "PageView"], 编码: "pgview"
case buttonClick // 解码/编码: "ButtonClick"
}关联值枚举的 key 也会自动遵循命名风格:
@Codable
@SnakeCase
enum Action {
case doSomething(userId: Int, userName: String)
// JSON: {"do_something": {"user_id": 42, "user_name": "John"}}
}注意:
@CodingCase含有at:或values:参数时, 不能与命名风格宏混用.
支持编解码的生命周期回调:
@Codable
class User {
var age: Int
func didDecode(from decoder: any Decoder) throws {
if age < 0 {
throw ReerCodableError(text: "Invalid age")
}
}
func willEncode(to encoder: any Encoder) throws {
// 在编码前进行处理
}
}
@Codable
struct Child: Equatable {
var name: String
mutating func didDecode(from decoder: any Decoder) throws {
name = "reer"
}
func willEncode(to encoder: any Encoder) throws {
print(name)
}
}提供便捷的 JSON 字符串和字典转换方法:
let jsonString = "{\"name\": \"Tom\"}"
let user = try User.decoded(from: jsonString)
let dict: [String: Any] = ["name": "Tom"]
let user2 = try User.decoded(from: dict)通过 @FlexibleType 启用基本数据类型之间的自动转换。可以应用在单个属性或整个类型上:
@Codable
struct User {
@FlexibleType
@CodingKey("is_vip")
var isVIP: Bool // 可以从 "1"、1、"true"、"yes" 解码为 true
@FlexibleType
@CodingKey("score")
var score: Double // 可以从 "100" 或 100 解码为 100.0
}
@Codable
@FlexibleType
struct Settings {
// 该类型的所有属性都将支持灵活类型转换
var isEnabled: Bool // 可以从数字或字符串解码
var count: Int // 可以从字符串解码
var amount: Double // 可以从字符串或整数解码
}如果你希望所有类型自动支持灵活类型转换而无需显式标注 @FlexibleType,可以在添加包依赖时启用 AutoFlexibleType trait:
// 在你的 Package.swift 中
.package(
url: "https://github.com/reers/ReerCodable.git",
from: "1.7.2",
traits: ["AutoFlexibleType"]
)启用此 trait 后,所有 @Codable 和 @Decodable 类型将自动支持灵活类型转换,就像每个类型都应用了 @FlexibleType 一样。这对于依赖后端 API 数据类型不一致的项目非常有用。
注意: 此功能需要 Swift 6.1+ 并且你的 Package.swift 中需要设置 swift-tools-version: 6.1。
重要: 修改 traits 后,必须删除 DerivedData/YourProject 并重启 Xcode 才能使更改生效。
通过 AnyCodable 实现对 Any 类型的编解码:
@Codable
struct Response {
var data: AnyCodable // 可以存储任意类型的数据
var metadata: [String: AnyCodable] // 相当于[String: Any]类型
}AnyCodable 提供了便捷的下标语法糖,支持链式访问嵌套的 JSON 结构:
let json = AnyCodable([
"users": [
["name": "Alice", "age": 25],
["name": "Bob", "age": 30]
],
"total": 2
])
// 链式访问嵌套结构
let firstName = json["users"][0]["name"].string // "Alice"
let secondAge = json["users"][1]["age"].int // 30
let total = json["total"].int // 2
// 安全访问不存在的路径,返回 null 而不会崩溃
let invalid = json["users"][5]["name"].isNull // true
let missing = json["nonexistent"].isNull // trueAnyCodable 提供了多种类型转换属性,方便快速获取对应类型的值:
| 属性 | 返回类型 | 说明 |
|---|---|---|
.bool |
Bool? |
转换为布尔值 |
.int |
Int? |
转换为整数 |
.uint |
UInt? |
转换为无符号整数 |
.double |
Double? |
转换为双精度浮点数 |
.string |
String? |
转换为字符串 |
.array |
[Any]? |
转换为数组 |
.dict |
[String: Any]? |
转换为字典 |
.dictArray |
[[String: Any]]? |
转换为字典数组 |
.isNull |
Bool |
检查是否为 null |
.data |
Data? |
转换为 JSON Data |
let json = AnyCodable([
"name": "phoenix",
"age": 33,
"isVIP": true,
"score": 99.5,
"tags": ["swift", "ios"],
"address": ["city": "Beijing", "country": "China"]
])
// 使用类型转换属性
let name = json["name"].string // "phoenix"
let age = json["age"].int // 33
let isVIP = json["isVIP"].bool // true
let score = json["score"].double // 99.5
let tags = json["tags"].array // ["swift", "ios"]
let address = json["address"].dict // ["city": "Beijing", "country": "China"]
// 创建和检查 null 值
let nullValue = AnyCodable.null
print(nullValue.isNull) // true@Codable
@DefaultInstance
struct ImageModel {
var url: URL
}
@Codable
@DefaultInstance
struct User5 {
let name: String
var age: Int = 22
var uInt: UInt = 3
var data: Data
var date: Date
var decimal: Decimal = 8
var uuid: UUID
var avatar: ImageModel
var optional: String? = "123"
var optional2: String?
}会生成以下实例
static let `default` = User5(
name: "",
age: 22,
uInt: 3,
data: Data(),
date: Date(),
decimal: 8,
uuid: UUID(),
avatar: ImageModel.default,
optional: "123",
optional2: nil
)@DefaultInstance
@Codable
struct NetResponse<Element: Codable> {
let data: Element?
let msg: String
private(set) var code: Int = 0
}使用 Copyable 为模型生成 copy 方法
@Codable
@Copyable
public struct Model6 {
var name: String
let id: Int
var desc: String?
}
@Codable
@Copyable
class Model7<Element: Codable> {
var name: String
let id: Int
var desc: String?
var data: Element?
}生成如下 copy 方法, 可以看到, 除了默认 copy, 还可以对部分属性进行更新
public func copy(
name: String? = nil,
id: Int? = nil,
desc: String? = nil
) -> Model6 {
return .init(
name: name ?? self.name,
id: id ?? self.id,
desc: desc ?? self.desc
)
}
func copy(
name: String? = nil,
id: Int? = nil,
desc: String? = nil,
data: Element? = nil
) -> Model7 {
return .init(
name: name ?? self.name,
id: id ?? self.id,
desc: desc ?? self.desc,
data: data ?? self.data
)
}@Decodable
struct Item: Equatable {
let id: Int
}
@Encodable
struct User3: Equatable {
let name: String
}
将某个嵌套属性在编解码时“拍平”,使其字段与父类型位于同一层级进行编码/解码。
@Codable
struct User {
var name: String
var age: Int = 0
@FlatCoding
var address: Address
}
@Codable
struct Address {
var country: String
var city: String
}
// 输入
let dict: [String: Any] = [
"name": "phoenix",
"age": 34,
"country": "China",
"city": "Beijing"
]
let model = try User.decoded(from: dict)
// model == User(name: "phoenix", age: 34, address: Address(country: "China", city: "Beijing"))@Codable 完全兼容继承自 NSObject 的类,宏会自动检测并正确处理 super.init() 的调用:
// 直接继承 NSObject
@Codable
public class Message: NSObject {
let title: String
let content: String
}
// 继承自 NSObject 子类
@Codable
class Article: NSObject {
let title: String
var content: String = ""
}
@InheritedCodable
class NewsArticle: Article {
let source: String
var publishDate: String = ""
}所有 @Codable 的特性(如 @CodingKey、@SnakeCase、@FlexibleType 等)都可以与 NSObject 子类一起使用。
以上示例展示了 ReerCodable 的主要特性,这些特性可以帮助开发者大大简化编解码过程,提高代码的可读性和可维护性。