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

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(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
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 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.

Related

Swift generic T.Type becomes T.Protocol

Swift 5.1
I'm writing a class that has a generic parameter T, and one of its methods accepts a type as an argument, where the type extends from T.Type. See foo1 below:
public protocol P {
}
public class C: P {
}
public class X<T> {
public func foo1(_ t: T.Type) { // The function in question
}
public func foo2(_ t: P.Type) { // Note that this works as expected, but is not generic
}
}
public func test() {
let x = X<P>()
x.foo1(C.self) // ERROR Cannot convert value of type 'C.Type' to expected argument type 'P.Protocol'
x.foo2(C.self) // Works as expected, but is not generic
}
Now, foo1 works fine when T is a class. However, when T is a protocol (e.g. P), Swift seems to rewrite my function signature from T.Type to T.Protocol.
Why did it do this?
How do I instead get foo1 to accept a type that inherits from P?
Class X is used in a number of other places - any changes to it must not restrict or remove class parameter T nor reduce X's functionality, nor make explicit reference to C or P. (It would be acceptable to constrain T to exclude protocols that do not extend AnyObject; I don't know how to do that, though. It might also be acceptable to e.g. create a subclass of X that adds the ability to handle a protocol in T, but I'm not sure how to do that, either.)
For clarity, this class is used to register classes (t) that conform to some specified parent (T), for more complicated project reasons. (Note that classes are being registered, not instances thereof.) The parent is given at the creation of X, via the type parameter. It works fine for a T of any class, but I'd also like it to work for a T of any protocol - or at least for a T of any protocol P: AnyObject, under which circumstances foo1 should accept any subclass of P...the same way it works when T is a class.
Even though C is a P, but C.Type != P.Type, so the error.
But generics works in a bit different way, like in below examples:
public class X {
public func foo1<T>(_ t: T.Type) { // this way
}
public func foo3<T:P>(_ t: T.Type) { // or this way
}
public func foo2(_ t: P.Type) { // Note that this works as expected, but is not generic
}
}
public func test() {
let x = X()
x.foo1(C.self) // << works
x.foo3(C.self) // << works
x.foo2(C.self) // Works as expected, but is not generic
}

Why can't I pass a Protocol.Type to a generic T.Type parameter?

I was working with Swinject and a problem is bugging me. I have been stuck one this for almost an entire day. I suspect this is due to Swift being a statictly typed language but I'm not entirely sure.
I summarized my problem in this playground
protocol Protocol {}
class Class: Protocol {}
let test: Protocol.Type = Class.self
func printType(confromingClassType: Protocol.Type) {
print(confromingClassType)
}
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.self) // "Class"
printType(serviceType: Class.self) // "Class"
print(test) // "Class"
printType(confromingClassType: test) // "Class"
printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'"
I tried different solutions like test.self or type(of: test) but none of them work.
So I guess I can't call a function with a generic parameter provided as a variable ?
P.Type vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P, and a conforming type C:
A P.Protocol describes the type of a protocol itself (the only value it can hold is P.self).
A P.Type describes a concrete type that conforms to the protocol. It can hold a value of C.self, but not P.self because protocols don't conform to themselves (although one exception to this rule is Any, as Any is the top type, so any metatype value can be typed as Any.Type; including Any.self).
The problem you're facing is that for a given generic placeholder T, when T is some protocol P, T.Type is not P.Type – it is P.Protocol.
So if we jump back to your example:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
We cannot pass test as an argument to printType(serviceType:). Why? Because test is a P.Type; and there's no substitution for T that makes the serviceType: parameter take a P.Type.
If we substitute in P for T, the parameter takes a P.Protocol:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T, such as C, the parameter takes a C.Type:
printType(serviceType: C.self) // C.self is of type C.Type
Hacking around with protocol extensions
Okay, so we've learnt that if we can substitute in a concrete type for T, we can pass a C.Type to the function. Can we substitute in the dynamic type that the P.Type wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
The extension member callPrintType() on P has an implicit generic placeholder Self that's constrained to P. The implicit self parameter is typed using this placeholder.
When calling callPrintType() on a P.Type, Swift implicitly digs out the dynamic type that the P.Type wraps (this is the opening of the existential), and uses it to satisfy the Self placeholder. It then passes this dynamic metatype to the implicit self parameter.
So, Self will be satisfied by C, which can then be forwarded onto printType's generic placeholder T.
Why is T.Type not P.Type when T == P?
You'll notice how the above workaround works because we avoided substituting in P for the generic placeholder T. But why when substituting in a protocol type P for T, is T.Type not P.Type?
Well, consider:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
What if we substituted in P for T? If T.Type is P.Type, then what we've got is:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
which is illegal; we cannot assign P.self to P.Type, as it's of type P.Protocol, not P.Type.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P (rather than just one specific concrete conforming type) – you just want a P.Type parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's exactly what you have with printType(conformingClassType:):
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
You can pass test to it because it has a parameter of type P.Type. But you'll note this now means we cannot pass P.self to it, as it is not of type P.Type:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
I've ran your code in a playground, and it seems that this is the reason why it wont compile
let test: Protocol.Type = Class.self
If you remove the type declaration for test, the code will work and will print out Class.Type at line 15.
So the following code compiles and runs:
protocol Protocol {}
class Class: Protocol {}
let test = Class.self
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.Type.self) // "Class.Type"
printType(serviceType: Class.Type.self) // "Class.Type"
print(type(of: test)) // "Class.Type"
printType(serviceType: type(of: test)) // "Class.Type"
I hope this helps with your problem.
Edit
The error I am getting in the playground with the original code is the following:
Playground execution failed: error: Untitled Page 2.xcplaygroundpage:9:1: error: cannot invoke 'printType' with an argument list of type '(serviceType: Protocol.Type.Type)'
printType(serviceType: type(of: test)) // "Class.Type"
This means you are calling Type 2 times, that's why the code will not compile, because the type you are already calling the method with the argument of type Protocol.Type.
If you change the method's signature like this:
let test: Protocol.Type = Class.self
func printType<Service>(serviceType: Service) {
print(serviceType)
}
everything will compile and work right, printing Class.type
This is also the reason my first version of the answer will compile, since it will assign the right type for test can call .Type only once.

How is init() relevant in Swift protocol?

Protocols in Swift can declare the init() method in their definition. However, I can't think of any use case where this solves any problem other than forcing the conforming classes to define the init() as in the protocol. We can call the declared methods on the protocol type but init on protocol cannot be used to instantiate its object, which is its only purpose.
What problem does declaring init() method in a protocol solve?
I think the real utility comes when it's used as a constraint in a generic class o function. This is real code from one of my projects.
I declare a protocol with a init:
protocol JSONCreatable {
init(fromJson json: JSON)
}
Then, in a generic function where I return a class that conforms to that protocol:
import SwiftyJSON
extension JSON {
func asObject<T>() -> T? where T: JSONCreatable {
if isEmpty {
return nil
}
return T(fromJson: self)
}
func asArray<T>() -> [T] where T: JSONCreatable {
return array?.map{ json in T(fromJson: json) } ?? []
}
}
This allows me to do things like this:
let user: User = json["user"].asObject()
let results: [Element] = json["elements"].asArray()
It forces class to have init(data: data) from some data, example:
protocol JSONable {
init(data: JSON)
}
forces all classes, that are JSONable to have an initialiser from JSON, so you are always sure, that you can create an instance from JSON.
It's commonly used in order to allow for protocol extensions and generic placeholders constrained to protocols to call the initialiser on the given concrete type that conforms to the protocol. For example, consider RangeReplaceableCollection's default implementation of init<S : Sequence>(_ elements: S):
extension RangeReplaceableCollection {
// ...
/// Creates a new instance of a collection containing the elements of a
/// sequence.
///
/// - Parameter elements: The sequence of elements for the new collection.
public init<S : Sequence>(_ elements: S) where S.Iterator.Element == Iterator.Element {
self.init()
append(contentsOf: elements)
}
// ...
}
Without init() being defined as a protocol requirement of RangeReplaceableCollection, there's no way for the extension to know that we can call init() in order to create a new instance of the conforming type.
But it can also be used directly outside of generics and extensions – for example, it can be used to construct a new instance represented by a given existential metatype (the metatype of 'some concrete type that conforms to a protocol'):
protocol P {
init()
}
struct S : P {
init() {}
}
let s: P = S()
let s1 = type(of: s).init() // creates a new instance of S, statically typed as P.
In this example:
type(of: s) returns the dynamic type of s as P.Type (an existential metatype), as s is statically typed as P. Remember that type(of:) is a (T) -> T.Type operation.
init() constructs a new instance of the underlying concrete type, in this case S.
The new instance is statically typed as P (i.e boxed in an existential container).

Generic Constraints and Initializer Inheritance in Swift

I am trying to call an initializer required by protocol A on a type that both conforms to A and is a subclass of C.
All is fine and good if C is a base class. But as soon as C subclasses another class, say B, it breaks.
Here's what I am talking about:
protocol A {
init(foo: String)
}
class B {
init() {}
}
class C: B {}
func makeSomething<T: A>() -> T where T: B {
return T(foo: "Hi")
}
That works. But if I change where T: B to where T: C, I get the following error: argument passed to call that takes no arguments. It will only allow me to call Bs init.
Why can't I call this initializer? I understand that the super class has its own designated initializers that must be called. But that will be enforced when someone actually writes the class that subclasses B and conforms to A. (E.g. implementing init(Foo: String) and calling super.init(bar: Int) inside).
Any help here would be greatly appreciated. Thanks!
Swift provides a default initializer for your base class if it has all properties initialized but not incase of Generics because Swift may think its properties has not been initialized.
So when you constraint your return type as
func makeSomething<T: A>() -> T where T: C
It requires initialization for class C as Swift cannot provide a default initializer.But if your remove the where clause everything works fine
func makeSomething<T: A>() -> T {
return T(foo:"string")
}
If you want to return return T(foo: "Hi") :
You are getting error because class C doesn't have initializer that accepts init(foo: String)
Change your class C as
class C: A {
required init(foo: String) {
}
}
which provides at least one initializer that accepts the argument type.
So, remember if you don't subclass There is already one initializer doing your job and you dont get error.

Make function of generic class only accept argument that is same generic type but more constrained

I have a generic class that can be initialised as any type. I would like to add a function with a single parameter that takes a value that is both of the class's generic type and conforms to the Comparable protocol. Type conformance should be enforced pre-compile.
I would like to do something like this:
class Object<T> {
let value: T!
init (value: T) {
self.value = value
}
func doSomething<U where U: Comparable, U == T>(otherValue: U) {
// do something
}
}
Is this possible to do?
Unfortunately, no. You can't further specialize a generic type in a method - you'd need to add a top-level function for the behavior you want.
This is the reason Array doesn't have a pure myArray.sort() function, since there's no way to guarantee that the members of any Array instance will be Comparable. Instead, there's a top-level function with this signature:
func sort<T : Comparable>(inout array: [T])
Your top-level function would have a similar structure:
func doSomething<T: Comparable)(obj: Object<T>, otherValue: T) {
// ...
}
For reference, this is possible since Swift 2.0:
extension Object where T : Comparable {
func doSomething(otherValue: T) {
// do something
}
}