问题
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 compiles
func 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?
回答1:
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 metatype P.self: P.Protocol
and an adopter C
. There is a special case when T
is unconstrained that allows it to work in the first place.
By far the more common case is to constrain T: P
and require P: 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 be register<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.
来源:https://stackoverflow.com/questions/37707450/function-that-takes-a-protocol-and-a-conforming-class-instance-as-parameters