如何使用内部协议的初始化器在公共函数中构造新值?

时间:2017-02-28 16:59:44

标签: swift generics initialization swift-protocols

假设我想将一些功能公开为公共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])!
    }
}

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

假设我想提供一些服务,它接受这样的值并返回一个相同类型的新服务:

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

这不编译; T上没有合适的初始值设定项。

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

2 个答案:

答案 0 :(得分:1)

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

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查找完整代码。

答案 1 :(得分:0)

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

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

并公开Q,如果外部代码不应该使用_,则可以在其前面添加{。}}。