[Profile]
不是 的子类型[ProfileRepresentable]
。(有关此问题的相关但不同的版本,请参阅Swift Generics & Upcasting。)当作为参数传递或分配给变量时,它可以通过编译器提供的复制步骤进行转换,但这是作为特殊情况提供的非常常见的用途。一般不适用。
你应该如何解决这个问题取决于你想用这种类型做什么。
如果你有一个依赖 ProfilerRepresentable 的算法,那么 Asperi 的解决方案是理想的,也是我推荐的。但是那样做将不允许您创建 ProfileRepresentable 类型的变量或将 ProfileRepresentable 放入数组中。
如果您需要 ProfilerRepresentable 的变量或数组,那么您应该问问自己这些协议到底在做什么。哪些算法依赖于这些协议,以及 ProfileRepresentable 的哪些其他合理实现真正有意义?在许多情况下,ProfileRepresentable 应该只替换为一个简单的 Profile 结构,然后init
在不同的上下文中使用不同的方法来创建它。(如果您的真正问题看起来很像您的示例,并且 Asperi 的答案对您不起作用,这就是我的建议。)
最终,您可以创建类型橡皮擦(AnyProfile),但我建议首先探索所有其他选项(特别是重新设计您的组合方式)。如果您的目标是擦除复杂的或私有的类型(AnyPublisher),类型橡皮擦是完美的,但这通常不是人们伸手去拿它们时的意思。
但是设计这个需要知道一个更具体的目标。没有普遍适用的普遍答案。
查看您的评论,如果它们代表不同的事物,则同一实体具有多种类型是没有问题的。结构是值。可以同时使用 Double 和 Float 类型,即使每个 Float 也可以表示为 Double。因此,在您的情况下,您似乎只想要Profile
和PartialProfile
结构,以及一个可以让您将一个转换为另一个的 init。
struct Profile {
var firstName: String
var lastName: String
}
struct PartialProfile {
var firstName: String?
var lastName: String?
}
extension Profile {
init(_ partial: PartialProfile) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
}
}
extension PartialProfile {
init(_ profile: Profile) {
self.firstName = profile.firstName
self.lastName = profile.lastName
}
}
你可能有很多这样的东西,所以这可能会有点乏味。有很多方法可以解决这个问题,具体取决于您要解决的问题。(我建议从编写具体代码开始,即使它会导致大量重复,然后看看如何删除这些重复。)
一种可能有用的工具是Partial<Wrapped>
(受 TypeScript 的启发),它将创建任何非可选结构的“可选”版本:
@dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? }
set { storage[member] = newValue }
}
}
struct Profile {
var firstName: String
var lastName: String
var age: Int
}
var p = Partial<Profile>()
p.firstName = "Bob"
p.firstName // "Bob"
p.age // nil
还有一个类似的转换器:
extension Profile {
init(_ partial: Partial<Profile>) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
self.age = partial.age ?? 0
}
}
现在继续你的数组问题,在这些之间切换只是一张地图。
var partials: [Partial<Profile>] = ...
let profiles = partials.map(Profile.init)
(当然,您可以创建一个 Array 扩展以使其成为一种.asWrapped()
方便的方法。)
另一个方向在最简单的方法中略显乏味:
extension Partial where Wrapped == Profile {
init(_ profile: Profile) {
self.init()
self.firstName = profile.firstName
self.lastName = profile.lastName
self.age = profile.age
}
}
如果有很多类型,可能值得让 Partial 更复杂一点,这样你就可以避免这种情况。这是一种方法,它允许 Partial 仍然是可变的(我希望这将是有价值的),同时还允许它从包装的实例中被简单地映射。
@dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
private var wrapped: Wrapped?
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? ?? wrapped?[keyPath: member] }
set { storage[member] = newValue }
}
}
extension Partial {
init(_ wrapped: Wrapped) {
self.init()
self.wrapped = wrapped
}
}
我不喜欢这个解决方案;它有一个奇怪的怪癖,partial.key = nil
无法清除值。但是在我们得到KeyPathIterable之前,我没有一个很好的解决方案。但是根据您的确切问题,您可以采取其他一些路线。当然,如果 Partial 不可变,事情会变得更简单。
关键是这里不需要协议。只是值和结构,并在需要时在它们之间进行转换。深入研究@dynamicMemberLookup
。如果您的问题非常动态,那么您可能只需要更多动态类型。