Generic Constraints and Initializer Inheritance in Swift - 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.

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 does second initialiser in base class break compilation?

Given a base class, a derived class and an extension with a convenience initializer, the compiler throws an error if a second initializer is added to the base class like in the following sscce
#!/usr/bin/env swift
class A {
required init(a : Int){}
init(b: Int){} // when removing this initializer everything works fine
}
class B: A {
required init(a : Int){ super.init(a: a) }
}
extension A {
convenience init(c : Int) { self.init(a: c) }
}
let b: B = B(c: 1)
With the two initializers in the base class the following error is thrown:
... error: incorrect argument label in call (have 'c:', expected 'a:')
let b: B = B(c: 1)
^~
a
Apart from an error message which is not very helpful in this case, I am not quite sure if this is expected behaviour or a bug in swift.
Swift version info:
Apple Swift version 5.0.1 (swiftlang-1001.0.82.4 clang-1001.0.46.5)
Target: x86_64-apple-darwin18.5.0
From Automatic Initializer Inheritance:
Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
Without init(b:) in class A, class B automatically inherits init(c:) from the extension and everything works.
But when you add init(b:) to class A, then class B no longer follows either rule and therefore class B no longer automatically inherits the init(c:) from the extension. This results in the error since class B now only has the one init(a:) initializer and no others.
You can fix the problem by overriding init(b:) in class B. With that in place, class B again automatically gets the init(c:) from the extension.

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

inheritance and polymorphism in swift

I have the following problem:
Class A - super class.
Class A protocol:
has method ->
func test(params: GeneralParams, completionBlock: GeneralCompletionBlock)
GeneralParams is super class and has the following 2 subclasses: BParams, CParams.
now i have 2 more classes:
class B: A, A protocol
class C: A, A protocol
I want class B, C to use test function but with different class for their params for class B i want to use BParams and different completion block and for C the same thing. i want to have the same method for both with different parameters and implementation for both.
Whats the best solution for this situation?
Since Swift allows method overload, the best practice here would be to overload a method to suite your needs in a new class.
For instance:
class B: A{
func test(params: BParams, completionBlock: GeneralCompletionBlock){
...
}
}
class C: A{
func test(params: CParams, completionBlock: GeneralCompletionBlock){
...
}
}
I want class B, C to use test function but with different class for their params
The most important rule of subclassing is substitutability. If B is a kind of A, then everywhere an A can be used, it must be legal to use a B. So every subclass of A must support this method:
func test(params: GeneralParams, completionBlock: GeneralCompletionBlock)
It cannot restrict this method to other types (not even to subtypes of these). If it did, then there would be places that I could use A, but couldn't use B.
B is free to extend A and add new methods. So, as #tmac_balla suggests, you can add an overload that will be selected for subtypes. For example:
class AParam {}
class BParam: AParam {}
class A {
func f(param: AParam) {print("A")}
}
class B: A {
func f(param: BParam) {print("B")}
}
B().f(AParam()) // "A"
B().f(BParam()) // "B"
But B must still support being passed the superclass.
Most of the time in Swift, this is the wrong way to go about things. Subclassing introduces lots of complexity that you usually don't want. Instead you generally want protocols and generics in Swift. For example, rather than A, B, and C, you would just have a generic A and a protocol.
protocol Param {
var name: String { get }
}
struct AParam: Param {
let name = "A"
}
struct BParam: Param {
let name = "B"
}
struct S<P: Param> {
func f(param: P) {print(param.name)}
}
let a = S<AParam>()
a.f(AParam()) // "A"
// a.f(BParam()) // error; we've restricted the type it can take
let b = S<BParam>()
b.f(BParam()) // "B"

How do I specify that a non-generic Swift type should comply to a protocol?

I'd like to implement a Swift method that takes in a certain class type, but only takes instances of those classes that comply to a specific protocol. For example, in Objective-C I have this method:
- (void)addFilter:(GPUImageOutput<GPUImageInput> *)newFilter;
where GPUImageOutput is a particular class, and GPUImageInput is a protocol. Only GPUImageOutput classes that comply to this protocol are acceptable inputs for this method.
However, the automatic Swift-generated version of the above is
func addFilter(newFilter: GPUImageOutput!)
This removes the requirement that GPUImageOutput classes comply with the GPUImageInput protocol, which will allow non-compliant objects to be passed in (and then crash at runtime). When I attempt to define this as GPUImageOutput<GPUImageInput>, the compiler throws an error of
Cannot specialize non-generic type 'GPUImageOutput'
How would I do such a class and protocol specialization in a parameter in Swift?
Is swift you must use generics, in this way:
Given these example declarations of protocol, main class and subclass:
protocol ExampleProtocol {
func printTest() // classes that implements this protocol must have this method
}
// an empty test class
class ATestClass
{
}
// a child class that implements the protocol
class ATestClassChild : ATestClass, ExampleProtocol
{
func printTest()
{
println("hello")
}
}
Now, you want to define a method that takes an input parameters of type ATestClass (or a child) that conforms to the protocol ExampleProtocol.
Write the method declaration like this:
func addFilter<T where T: ATestClass, T: ExampleProtocol>(newFilter: T)
{
println(newFilter)
}
Your method, redefined in swift, should be
func addFilter<T where T:GPUImageOutput, T:GPUImageInput>(newFilter:T!)
{
// ...
}
EDIT:
as your last comment, an example with generics on an Enum
enum OptionalValue<T> {
case None
case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)
Specialized with protocol conformance:
enum OptionalValue<T where T:GPUImageOutput, T:GPUImageInput> {
case None
case Some(T)
}
EDIT^2:
you can use generics even with instance variables:
Let's say you have a class and an instance variable, you want that this instance variable takes only values of the type ATestClass and that conforms to ExampleProtocol
class GiveMeAGeneric<T: ATestClass where T: ExampleProtocol>
{
var aGenericVar : T?
}
Then instantiate it in this way:
var child = ATestClassChild()
let aGen = GiveMeAGeneric<ATestClassChild>()
aGen.aGenericVar = child
If child doesn't conform to the protocol ExampleProtocol, it won't compile
this method header from ObjC:
- (void)addFilter:(GPUImageOutput<GPUImageInput> *)newFilter { ... }
is identical to this header in Swift:
func addFilter<T: GPUImageOutput where T: GPUImageInput>(newFilter: T?) { ... }
both method will accept the same set of classes
which is based on GPUImageOutput class; and
conforms GPUImageInput protocol; and
the newFilter is optional, it can be nil;
From Swift 4 onwards you can do:
func addFilter(newFilter: GPUImageOutput & GPUImageInput)
Further reading:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
http://braking.github.io/require-conformance-to-multiple-protocols/
Multiple Type Constraints in Swift