-1

我将通过一个例子来解释它。我们有一个强制拥有firstNamelastName喜欢的协议:

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

到目前为止,一切都很好

现在对于s列表也有类似的流程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 }
}

顺便说一句,我怎样才能实现数组的目标?

4

2 回答 2

1

[Profile]不是 的子类型[ProfileRepresentable]。(有关此问题的相关但不同的版本,请参阅Swift Generics & Upcasting。)当作为参数传递或分配给变量时,它可以通过编译器提供的复制步骤进行转换,但这是作为特殊情况提供的非常常见的用途。一般不适用。

你应该如何解决这个问题取决于你想用这种类型做什么。

如果你有一个依赖 ProfilerRepresentable 的算法,那么 Asperi 的解决方案是理想的,也是我推荐的。但是那样做将不允许您创建 ProfileRepresentable 类型的变量或将 ProfileRepresentable 放入数组中。

如果您需要 ProfilerRepresentable 的变量或数组,那么您应该问问自己这些协议到底在做什么。哪些算法依赖于这些协议,以及 ProfileRepresentable 的哪些其他合理实现真正有意义?在许多情况下,ProfileRepresentable 应该只替换为一个简单的 Profile 结构,然后init在不同的上下文中使用不同的方法来创建它。(如果您的真正问题看起来很像您的示例,并且 Asperi 的答案对您不起作用,这就是我的建议。)

最终,您可以创建类型橡皮擦(AnyProfile),但我建议首先探索所有其他选项(特别是重新设计您的组合方式)。如果您的目标是擦除复杂的或私有的类型(AnyPublisher),类型橡皮擦是完美的,但这通常不是人们伸手去拿它们时的意思。

但是设计这个需要知道一个更具体的目标。没有普遍适用的普遍答案。


查看您的评论,如果它们代表不同的事物,则同一实体具有多种类型是没有问题的。结构是值。可以同时使用 Double 和 Float 类型,即使每个 Float 也可以表示为 Double。因此,在您的情况下,您似乎只想要ProfilePartialProfile结构,以及一个可以让您将一个转换为另一个的 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。如果您的问题非常动态,那么您可能只需要更多动态类型。

于 2020-07-18T14:11:22.700 回答
1

这是可能的解决方案。使用 Xcode 12 / swift 5.3 测试

protocol ProfilerRepresentable {
    associatedtype T:ProfileRepresentable
    var profiles: [T] { get }
}

extension Profiler: ProfilerRepresentable { }
struct Profiler {
    var profiles: [Profile]
}
于 2020-07-18T13:30:05.327 回答