Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
790 views
in Technique[技术] by (71.8m points)

swift - Function that takes a protocol and a conforming class (!) instance as parameters

I am trying to figure out how to define a function which takes the following two parameters:

  1. A protocol.
  2. 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(proto: P.self, obj: C()) // (1)

but these should not compile:

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

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(proto: 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

  1. A class protocol.
  2. 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...