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

Posted

技术标签:

【中文标题】如何使用来自内部协议的初始化程序在公共函数中构造新值?【英文标题】:How to construct a new value in a public function using initializers from an internal protocol? 【发布时间】:2017-02-28 16:59:43 【问题描述】:

假设我想将一些功能公开为公共 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 上没有合适的初始化程序。

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

【问题讨论】:

【参考方案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。

【讨论】:

我偶然发现了这个;语法完成显示.init(...) 可用于type(of:) 的结果。我们可以通过其他方式获得这样的值吗?无论如何这个函数的返回类型是什么? type(of:)技术上不是一个函数——它是dynamic type expression。它评估的静态类型取决于您使用它的静态类型。例如,在String 实例上使用它会产生String.Type,在Any 实例上使用它会产生Any.Type 演员的静态版本是(T.self as! Q.Type).init(from: ... btw。无论T 的静态类型是什么,它都会创建一个新实例,而不是动态类型。 @Hamish 酷,谢谢!我将不得不考虑是否需要静态类型的动态。不过,我认为是动态的……静态类型是P,它不会转换为 Q。 @Hamish 我扩展了我的答案以纳入您的评论;对于没有获得T 类型值的方法,我们需要这种转换!但是,无论如何,我发现它似乎可以解析为动态类型;或者,如果它是静态的,编译器就会发挥它的魔力。无论哪种方式,都会调用正确的构造函数。【参考方案2】:

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

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

并公开Q,如果外部代码不应该使用它,可能会在它前面加上_

【讨论】:

没有。我不想Q 公开,这就是重点。否则我可以做P: Q 并完成它。而且我绝对不会用标识符约定替换可访问性定义;这不是 php _ 前缀在 Swift 中很常见(例如在标准库中)。这是强制转换(绕过类型系统)和_ 前缀(绕过访问系统)之间的权衡。 除了大量以__ 为前缀的***函数(看起来像“不要在 Swift 中使用我,无论如何),我不知道标准中有任何这样的名称图书馆。是你的意思吗?如果不是,你能举个例子吗? 不,我的意思是公共协议、函数和类型上的单下划线前缀。例如,在浏览this 目录中的文件时,很难不注意到它们。 你能指出一个具有这样名字的特定public成员吗?因为我检查了一些文件并没有找到。确实有 internal 成员拥有_-names,但这些成员不支持您的观点。

以上是关于如何使用来自内部协议的初始化程序在公共函数中构造新值?的主要内容,如果未能解决你的问题,请参考以下文章

java面向对象入门-java构造方法

在构造函数中初始化公共静态最终变量

静态构造函数、内部构造函数和公共构造函数有啥区别?

具有内部函数和属性的 Swift 公共协议

内部类的公共构造函数是啥意思[重复]

iOS 应用程序看不到来自框架的公共协议