将协议和符合标准的类 (!) 实例作为参数的函数
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<T>(proto: Any.Type, obj: T)
,因为它会做的所有好事。
理论上 Swift 可以支持对元类型的约束,ala register<T: AnyObject, U: AnyProtocol where T.Type: U>(proto: U, obj: T)
但我怀疑它在许多情况下是否有用。
【讨论】:
以上是关于将协议和符合标准的类 (!) 实例作为参数的函数的主要内容,如果未能解决你的问题,请参考以下文章
Python 类的 init 函数将自己的类的实例作为默认参数
设计一个函数,它接受不定数量的参数,这是参数都是函数。这些函数都接受一个回调函数作为参数,按照回调函数被调用的顺序返回函数名