DynamicCodable

基于 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 将可以访问 DetailScreenRouteid 属性,而无需解包可选值,并且仅获取所需的信息。

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)
    }
}

约束