如何使用来自内部协议的初始值设定项在 public 函数中构造新值?

How to construct a new value in a public function using initializers from an internal protocol?

假设我想公开一些功能 public API,但部分实现涉及内部协议:

public protocol P {
    var foo: Int { get }
}

internal protocol Q {
    init(from: [String])
}

public struct S: P, Q {
    public var foo: Int = 0
    public init() {}    

    internal init(from: [String]) {
        precondition(from.count > 0)
        self.foo = Int(from[0])!
    }
}

这将是一些数据对象,只有我自己的模块可以构造(从一些数据表示)但是模块的用户可以出于他们自己的目的使用。

假设我想提供一些具有这样值的服务和returns一个相同类型的新服务:

public class ProviderOfThings {
    public func map<T: P>(before: T) -> T {
        return T(from: [String(before.foo + 1)])
    }
}

这不编译; T.

上没有合适的初始值设定项

如何以通用方式调用我(内部)知道的构造函数?

我们可以将值转换为内部协议类型并从那里访问初始化器:

public static func map<T: P>(before: T) -> T {
    return type(of: before as! Q).init(
        from: [String(before.foo + 1)]
    ) as! T
}

我们甚至可以不用类型的值(感谢,Hamish!):

public static func make<T: P>() -> T {
    return (T.self as! Q.Type).init(
        from: ["0"]
    ) as! T
}

如果 P 有(或可能有)不符合 Q 的实现,则需要注意对 Q 的强制转换,显然:

guard let beforeQ = before as? Q else { ... }
// or
guard before is Q else { ... }

// respectively
guard T.self is Q.Type else { ... }

另一个转换 as! T 是安全的:毕竟我们调用了 T 的初始值设定项。因此,如果有所防范,该解决方案不会导致运行时错误。

查找带有示例的完整代码 here

我认为最好的方法是声明 map 如下:

public func map<T: P & Q>(before: T) -> T

并生成 Q public,如果它不应该被外部代码使用,可能会在它前面加上 _ 前缀。