基于 Codable
的 Swift 属性包装器,用于解码(和编码)在应该被解码的 (JSON) 数据中定义的类型。
DynamicCodable
提供了一种方式来(反)序列化属性,通过将它们包装在 @DymamicEncodable
、@DymamicDecodable
或它们的组合 @DynamicCodable
中。
protocol Route: TypeIdentifiable {}
struct HomeScreen: Codable {
@DynamicCodable var `self`: Route
@DynamicCodable var routes: [Route]
@DynamicCodable var someRouteDict: [String: Route]
// Optionals that are missing in the JSON (and are not replaced by `null`)
// can't be wrapped in `@DynamicCodable`, see "Constraints".
let someOptionalRoute: DynamicCodable<Route>?
}
HomeScreen
的 JSON 可能如下所示
{
"self": {
"type": "homescreen"
},
"routes": [
{
"type": "detailscreen",
"id": "550121C5-3D8F-4AC8-AB14-BDF7E6D11626"
},
{
"type": "detailscreen",
"id": "697167E5-90EE-4CD2-879D-EAF49064F400"
}
],
"someRouteDict": {
"profilescreen": {
"type": "profilescreen",
"id": "84874B0F-1F41-4380-B3C6-CC53A0DE5453",
"tracking": {
"some_tracking_service": {
"some_tracking_property": "some_tracking_value"
}
}
}
}
}
Route
具有共同的 type
字段(这也是 TypeIdentifiable
的唯一要求),用于标识应该被反序列化的实际类型。为了使此功能生效,类型必须在 DynamicDecodableRegistry
中注册,并带有各自的标识符。
DynamicDecodableRegistry.register(DetailScreenRoute.self, typeIdentifier: DetailScreenRoute.type)
DynamicDecodableRegistry.register(HomeScreenRoute.self, typeIdentifier: HomeScreenRoute.type)
DynamicDecodableRegistry.register(ProfileScreenRoute.self, typeIdentifier: ProfileScreenRoute.type)
为了仅使用 Swift 的 Codable
反序列化如上 JSON,可以定义一个 Route
结构体,该结构体将所有可能的 JSON 字段定义为单个位置的可选属性。在模块化设置中,路由目标/功能(如详细信息屏幕或主屏幕)在不同的模块中分离,这可能不是理想的解决方案。另一种选择可能是不完全使用路由模型,而只是反序列化字典。
通过使用 DynamicCodable
,可以利用 Swift 的类型系统来创建清晰的接口,这些接口使用定义了各个可选和非可选属性的类型。在一个可能的路由设置中,例如以下示例,DetailScreenRouteHandler
将可以访问 DetailScreenRoute
的 id
属性,而无需解包可选值,并且仅获取所需的信息。
protocol RouteHandler {
associatedtype ConcreteRoute: Route
associatedtype Content: View
func view(for route: ConcreteRoute) -> Content
}
protocol Router {
func callAsFunction(_ route: Route)
func register<Handler: RouteHandler>(_ handler: Handler)
}
// Interface module "HomeScreen"
struct HomeScreenRoute: Route {
static let type = "homescreen"
let type: String
}
// Implementation module "HomeScreenImplementation"
struct HomeScreenRouteHandler: RouteHandler {
func view(for route: HomeScreenRoute) -> Color {
.red
}
}
// Interface module "DetailScreen"
struct DetailScreenRoute: Route {
static let type = "detailscreen"
let type: String
let id: UUID
}
// Implementation module "DetailScreenImplementation"
struct DetailScreenRouteHandler: RouteHandler {
func view(for route: DetailScreenRoute) -> Text {
Text(route.id)
}
}
type
字段进行标识,这是 TypeIdentifiable
协议的要求。Value
类型来确定。DynamicDecodableRegistry
中注册才能进行解码,注册时需要使用 String 类型的标识符(然后将其与 type
字段的值进行匹配以进行解码)。Optional<DynamicCodable<Route>>
。这是因为在自动 Decodable
合成中,属性不被视为 Optional
,因为属性包装器本身是一个非可选值。