我将通过一个例子进行解释。我们有一个强制使用firstName
和lastName
的协议,例如:
protocol ProfileRepresentable {
var firstName: String { get }
var lastName: String { get }
}
我们将要使用的类型具有这两种,但形式可选:
struct Profile {
var firstName: String?
var lastName: String?
}
因此,在遵循ProfileRepresentable
之后,我们将扩展ProfileRepresentable
并尝试返回该值和一个用于nil
状态的默认值:
extension Profile: ProfileRepresentable { }
extension ProfileRepresentable where Self == Profile {
var firstName: String { self.firstName ?? "NoFirstName" }
var lastName: String { self.lastName ?? "NoLastName" }
}
现在,Profile
的列表具有类似的流程。
protocol ProfilerRepresentable {
var profiles: [ProfileRepresentable] { get }
}
struct Profiler {
var profiles: [Profile]
}
符合ProfilerRepresentable
不会不自动按预期完成实施(因为Profile
已经符合ProfileRepresentable
)
extension Profiler: ProfilerRepresentable { }
按照先前的模式,扩展ProfilerRepresentable
不能按预期方式工作,并且会发出警告:
⚠️通过此函数的所有路径都将自行调用
extension ProfilerRepresentable where Self == Profiler {
var profiles: [ProfileRepresentable] { self.profiles }
}
我怎样才能实现数组的目标?
答案 0 :(得分:1)
这是可能的解决方案。经过Xcode 12 / swift 5.3的测试
protocol ProfilerRepresentable {
associatedtype T:ProfileRepresentable
var profiles: [T] { get }
}
extension Profiler: ProfilerRepresentable { }
struct Profiler {
var profiles: [Profile]
}
答案 1 :(得分:1)
[Profile]
不是[ProfileRepresentable]
的子类型。 (有关此问题的相关但不同的版本,请参见Swift Generics & Upcasting。)作为参数传递或分配给变量时,可以通过编译器提供的复制步骤进行转换,但这是为以下情况提供的特殊情况:那些非常普遍的用途。它通常不适用。
您应如何解决此问题取决于您要对这种类型进行精确的操作。
如果您有一种依赖于ProfilerRepresentable的算法,那么Asperi的解决方案是理想的选择,也是我推荐的解决方案。但是那样做将不允许您创建ProfileRepresentable类型的变量或将ProfileRepresentable放入数组中。
如果您需要ProfilerRepresentable的变量或数组,则应问自己这些协议实际上在做什么。哪些算法依赖于这些协议,ProfileRepresentable的其他合理实现还有意义吗?在许多情况下,应该将ProfileRepresentable替换为简单的Profile结构,然后使用不同的init
方法在不同的上下文中创建它。 (这是我的建议,如果您的实际问题看起来很像您的示例,而Asperi的答案对您不起作用。)
最终,您可以创建类型的橡皮擦(AnyProfile),但是我建议您先探索所有其他选项(特别是重新设计合成方式)。如果您的目标是擦除复杂类型或私有类型(AnyPublisher),则类型擦除器是完美的选择,但这通常不是人们想要的那种意思。
但是要设计此功能,需要知道更具体的目标。没有普遍适用的普遍答案。
查看您的评论,如果同一实体代表不同的事物,则具有多个类型是没有问题的。结构是价值。可以同时使用Double和Float类型,即使每个Float也可以表示为Double。因此,在您的情况下,您似乎只需要Profile
和PartialProfile
结构,以及一个使您可以将一个转换为另一个的初始化。
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
。如果您的问题非常动态,那么您可能只需要更多动态类型。