将协议和符合类(!)实例作为参数的函数 [英] Function that takes a protocol and a conforming class (!) instance as parameters
问题描述
我试图弄清楚如何定义一个函数,该函数需要以下
两个参数:
- 协议。
- 符合该协议的 class (a 引用类型)的实例。 ol>
例如,给定
协议P {}
class C:P {} // Class,符合P
class D {} // Class,不符合P
struct E:P {} //符合P
这应该编译:
register(P.self,obj:C())//(1)
不应该编译:
register(P.self,obj:D())//(2)D不符合P
寄存器(P.self,obj:E())//(3)E不是类
如果我们放弃第二个参数是一个类实例的条件很容易:
func寄存器< T(原型:T.Type,obj:T){
// ...
}
但是这会接受struct(value type)在
(3)
中也是如此。
这看起来很有希望并且编译
func寄存器< T:AnyObject>(proto:T.Type,obj:T ){
// ...
}
但没有
)(1)
,(2)
,(3)
编译(例如
register(P.self,obj:C())//(1)
//错误:无法用类型为'(P.Protocol,obj:C)'的参数列表'调用'register''
我假设编译器错误的原因与
相同
另一次失败的尝试是
func寄存器< T>(原型:T.Type,obj:协议< T,AnyObject>){}
//错误:非协议类型'T'不能用于'protocol< ...>'
一个可行的替代方案是一个函数,它Ť akes作为参数
- 类协议。
- 一种符合该协议的类型。
这个问题是如何限制第一个参数,使得只有
类协议公认。
背景:最近我偶然发现了
SwiftNotificationCenter
实现面向协议的类型安全通知机制的项目。
它有一个
register
方法如下所示:
public class NotificationCenter {
public static func register< T>(protocolType:T.Type,observer:T){
guard let object = observer as? AnyObject其他{
fatalError(期待引用类型,但发现值类型:\(观察者))
}
// ...
}
// ...
}
观察者被储存作为弱引用,这就是为什么他们
必须是引用类型,即类的实例。
但是,这只在运行时检查,我不知道如何进行编译时检查。
我是否缺少简单/明显的东西?
解决方案你不能直接做你想做的事情。它与引用类型无关,这是因为任何约束都会导致
T
存在,因此当您引用协议的元类型<$时,无法在调用站点满足它们c $ c> P.self:P.Protocol 和采用者C
。有一个特殊情况,当T
不受限制时,允许它首先工作。
到目前为止更常见的情况是限制
T:P
并且要求P:class
,因为只是 您可以使用任意协议的元类型来做的事情是将名称转换为字符串。在这种狭隘的情况下,它恰好是有用的,但就是这样;该签名可能是注册< T>(原型:Any.Type,obj:T)
,它可以完成所有的工作。理论上Swift可以支持对元类型的约束,alaregister< T:AnyObject,U:AnyProtocol,其中T.Type:U>(原型: U,obj:T)
但我怀疑它会在很多情况下有用。I am trying to figure out how to define a function which takes the following two parameters:
- A protocol.
- An instance of a class (a reference type) conforming to that protocol.
For example, given
protocol P { } class C : P { } // Class, conforming to P class D { } // Class, not conforming to P struct E: P { } // Struct, conforming to P
this should compile:
register(P.self, obj: C()) // (1)
but these should not compile:
register(P.self, obj: D()) // (2) D does not conform to P register(P.self, obj: E()) // (3) E is not a class
It is easy if we drop the condition that the second parameter is a class instance:
func register<T>(proto: T.Type, obj: T) { // ... }
but this would accept the struct (value type) in
(3)
as well. This looked promising and compilesfunc register<T: AnyObject>(proto: T.Type, obj: T) { // ... }
but then none of
(1)
,(2)
,(3)
compile anymore, e.g.register(P.self, obj: C()) // (1) // error: cannot invoke 'register' with an argument list of type '(P.Protocol, obj: C)'
I assume that the reason for the compiler error is the same as in Protocol doesn't conform to itself?.
Another failed attempt is
func register<T>(proto: T.Type, obj: protocol<T, AnyObject>) { } // error: non-protocol type 'T' cannot be used within 'protocol<...>'
A viable alternative would be a function which takes as parameters
- A class protocol.
- An instance of a type conforming to that protocol.
Here the problem is how to restrict the first parameter such that only class protocols are accepted.
Background: I recently stumbled over the SwiftNotificationCenter project which implements a protocol-oriented, type safe notification mechanism. It has a
register
method which looks like this: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)") } // ... } // ... }
The observers are then stored as weak references, and that's why they must be reference types, i.e. instances of a class. However, that is checked only at runtime, and I wonder how to make it a compile-time check.
Am I missing something simple/obvious?
解决方案You can't do what you are trying to do directly. It has nothing to do with reference types, it's because any constraints make
T
existential so it is impossible to satisfy them at the call site when you're referencing the protocol's metatypeP.self: P.Protocol
and an adopterC
. There is a special case whenT
is unconstrained that allows it to work in the first place.By far the more common case is to constrain
T: P
and requireP: class
because just about the only thing you can do with an arbitrary protocol's metatype is convert the name to a string. It happens to be useful in this narrow case but that's it; the signature might as well beregister<T>(proto: Any.Type, obj: T)
for all the good it will do.In theory Swift could support constraining to metatypes, ala
register<T: AnyObject, U: AnyProtocol where T.Type: U>(proto: U, obj: T)
but I doubt it would be useful in many scenarios.这篇关于将协议和符合类(!)实例作为参数的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- 类协议。