将协议和符合标准的类 (!) 实例作为参数的函数

Posted

技术标签:

【中文标题】将协议和符合标准的类 (!) 实例作为参数的函数【英文标题】:Function that takes a protocol and a conforming class (!) instance as parameters 【发布时间】:2016-06-08 15:58:46 【问题描述】:

我试图弄清楚如何定义一个函数,它采用以下内容 两个参数:

    协议。 符合该协议的引用类型)的实例。

例如,给定

protocol P  
class C : P   // Class, conforming to P
class D       // Class, not conforming to P
struct E: P   // Struct, conforming to P

这应该编译:

register(proto: P.self, obj: C()) // (1)

但这些不应该编译:

register(proto: P.self, obj: D()) // (2)  D does not conform to P
register(proto: P.self, obj: E()) // (3)  E is not a class

如果我们去掉第二个参数是类实例的条件就很容易了:

func register<T>(proto: T.Type, obj: T) 
    // ...

但这也将接受(3) 中的结构(值类型)。 这看起来很有希望并且可以编译

func register<T: AnyObject>(proto: T.Type, obj: T) 
    // ...

但是(1)(2)(3) 都不再编译,例如

register(proto: P.self, obj: C()) // (1)
// error: cannot invoke 'register' with an argument list of type '(P.Protocol, obj: C)'

假设编译器错误的原因与 Protocol doesn't conform to itself?.

另一个失败的尝试是

func register<T>(proto: T.Type, obj: protocol<T, AnyObject>)  
// error: non-protocol type 'T' cannot be used within 'protocol<...>'

一个可行的替代方案是一个以参数为参数的函数

    一个协议。 符合该协议的类型的实例。

这里的问题是如何限制第一个参数,使得只有 类协议被接受。

背景:我最近偶然发现了 SwiftNotificationCenter 实现面向协议的类型安全通知机制的项目。 它有一个 register 方法如下:

public class NotificationCenter 

    public static func register<T>(protocolType: T.Type, observer: T) 
        guard let object = observer as? AnyObject else 
            fatalError("expecting reference type but found value type: \(observer)")
        

        // ...
    

    // ...

观察者然后被存储为弱引用,这就是为什么他们 必须是引用类型,即类的实例。 但是,这仅在运行时检查,我想知道如何使其成为编译时检查。

我错过了一些简单/明显的东西吗?

【问题讨论】:

不理想(或完全是您的问题),但定义 protocol X: class 会使这更安全吗? 我以前注意到过这种行为——一旦你约束了一个泛型,看起来 Swift 将不再允许它采用抽象类型。在这种情况下,T 将是 P,这不是具体类型。我还注意到与协议相关类型的类似行为(一旦你约束它们,它们就只能采用具体类型)——事实上有一个bug report about that。 @sschale:如果您可以将函数限制为仅接受类协议(我也没有管理),那将有所帮助。 这是一个 GMTA 的例子,我也想弄清楚某种扩展,好吧,允许您注册为协议侦听器。 【参考方案1】:

你不能直接做你想做的事。它与引用类型无关,因为任何约束都使T 存在,因此当您引用协议的元类型P.self: P.Protocol 和采用者C 时,不可能在调用站点满足它们。当T 不受约束时,有一种特殊情况允许它首先工作。

到目前为止,更常见的情况是约束T: P 并要求P: class,因为您可以使用任意协议的元类型唯一 将名称转换为字符串。它恰好在这种狭窄的情况下很有用,但仅此而已;签名也可以是register&lt;T&gt;(proto: Any.Type, obj: T),因为它会做的所有好事。

理论上 Swift 可以支持对元类型的约束,ala register&lt;T: AnyObject, U: AnyProtocol where T.Type: U&gt;(proto: U, obj: T) 但我怀疑它在许多情况下是否有用。

【讨论】:

以上是关于将协议和符合标准的类 (!) 实例作为参数的函数的主要内容,如果未能解决你的问题,请参考以下文章

Python 类的 init 函数将自己的类的实例作为默认参数

通过函数传递未命名的类

JavaScript中的Function类型总结

指向继承类实例的指针作为函数参数

设计一个函数,它接受不定数量的参数,这是参数都是函数。这些函数都接受一个回调函数作为参数,按照回调函数被调用的顺序返回函数名

如何将符合协议的类声明为参数类型?