将协议和符合该协议的类实例作为参数的函数

9

我正在尝试找出如何定义一个函数,该函数接受以下两个参数:

  1. 协议。
  2. 符合该协议的一个类(引用类型)的实例。

例如,给定

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)'

我假设编译器错误的原因与协议不符合自身中的相同。

另一个失败的尝试是:

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

一个可行的替代方案是一个函数,它以以下参数为输入:
  1. 一个类协议。
  2. 符合该协议的类型的实例。
问题在于如何限制第一个参数,使其只接受类协议。
背景:最近我偶然发现了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 {} 是否会让这个更加安全呢? - sschale
我之前注意到过这种行为 - 一旦你限制了一个泛型,Swift似乎就不再允许它采用抽象类型。在这种情况下,T将是P,这不是一个具体类型。我还注意到协议关联类型有类似的行为(一旦你对它们进行约束,它们只能采用具体类型) - 实际上有一个关于此的错误报告 - Hamish
@sschale:如果你可以限制函数只接受类协议,那会很有帮助(我也没有做到这一点)。 - Martin R
这是一个GMTA的情况,我也想找出某种扩展,允许您注册为协议监听器。 - Fattie
1个回答

0
你不能直接做你试图做的事情。这与引用类型无关,而是因为任何约束都使得 T 存在性,因此在引用协议的元类型 P.self: P.Protocol 和一个采用者 C 的调用站点上满足它们是不可能的。有一种特殊情况,当 T 未受限制时,它才能起作用。
远远更常见的情况是约束 T: P 并要求 P: class,因为几乎你可以使用任意协议的元类型来将名称转换为字符串。它恰好在这个狭窄的情况下很有用,但仅此而已;签名可能与 register<T>(proto: Any.Type, obj: T) 相同。
理论上,Swift 可以支持对元类型进行约束,例如 register<T: AnyObject, U: AnyProtocol where T.Type: U>(proto: U, obj: T),但我怀疑它在许多场景中都没有用处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接