Implementing Swift protocol methods in a base class - swift

I have a Swift protocol that defines a method like the following:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
I have several different classes that will conform to this protocol:
class MyClass1 : MyProtocol { ... }
class MyClass2 : MyProtocol { ... }
class MyClass3 : MyProtocol { ... }
The implementation for the retrieve method in each subclass will be nearly identical. I'd like pull the common implementation of those functions into a shared superclass that conforms to the protocol:
class MyBaseClass : MyProtocol
{
class func retrieve(id:String) -> MyBaseClass?
}
class MyClass1 : MyBaseClass { ... }
class MyClass2 : MyBaseClass { ... }
class MyClass3 : MyBaseClass { ... }
The problem with this approach is that my protocol defines the return type of the retrieve method as type Self, which is what I really want in the end. However, as a result I cannot implement retrieve in the base class this way because it causes compiler errors for MyClass1, MyClass2, and MyClass3. Each of those classes must conform to the protocol that they inherit from MyBaseClass. But because the method is implemented with a return type of MyBaseClass and the protocol requires it to be of MyClass1, it says that my class doesn't conform to the protocol.
I'm wondering if there is a clean way of implementing a protocol method that references a Self type in one or more of its methods from within a base class. I could of course implement a differently-named method in the base class and then have each subclass implement the protocol by calling into its superclass's method to do the work, but that doesn't seem particularly elegant to me.
Is there a more straightforward approach that I'm missing here?

This should work:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
class MyBaseClass: MyProtocol {
required init() { }
class func retrieve(id:String) -> Self? {
return self()
}
}
required init() { } is necessary to ensure any subclasses derived from MyBaseClass has init() initializer.
Note that this code crashes Swift Playground. I don't know why. So try with real project.

Not sure what you're looking to accomplish here by just your example, so here's a possible solution:
protocol a : class {
func retrieve(id: String) -> a?
}
class b : a {
func retrieve(id: String) -> a? {
return self
}
}
The reasoning behind the
protocol a : class
declaration is so that only reference types can be extensions. You likely don't want to be passing around value types (struct) when you're dealing with your classes.

I have marked the answer from #rintaro as the correct answer because it did answer the question as I asked it. However, I have found this solution to be too limiting so I'm posting the alternate answer I found to work here for any others running into this problem.
The limitation of the previous answer is that it only works if the type represented by Self (in my example that would be MyClass1, MyClass2, or MyClass3) is used in a protocol or as the return type from a class method. So when I have this method
class func retrieve(id:String) -> Self?
everything works as I hoped. However, as I worked through this I realized that this method now needs to be asynchronous and can't return the result directly. So I tried this with the class method:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
I can put this method into MyProtocol but when I try to implement in MyBaseClass I get the following compiler error:
Error:(57, 36) 'Self' is only available in a protocol or as the result of a class method; did you mean 'MyBaseClass'?
So I really can't use this approach unless the type referenced by Self is used in very specific ways.
After some experimentation and lots of SO research, I was finally able to get something working better using generics. I defined the method in my protocol as follows:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
and then in my base class I do the following:
class MyBaseClass : MyProtocol {
class func retrieve<T:MyBaseClass>(id:String, successCallback: (T) -> (), failureCallback: (NSError) -> ()) {
// Perform retrieve logic and on success invoke successCallback with an object of type `T`
}
}
When I want to retrieve an instance of the type MyClass1, I do the following:
class MyClass1 : MyBaseClass {
func success(result:MyClass1} {
...
}
func failure(error:NSError) {
...
}
class func doSomething {
MyClass1.retrieve("objectID", successCallback:success, failureCallback:failure)
}
With this implementation, the function type for success tells the compiler what type should be applied for T in the implementation of retrieve in MyBaseClass.

Related

Why do I need to force cast a property to a generic method with the same signature as the property?

This is my code:
class GenericClass<T: UITableViewCell> {
let enumProperty = SomeEnum.myValue
enum SomeEnum {
case myValue
}
func callOtherClass() {
OtherClass.handle(property: enumProperty) // Compile error
}
}
class OtherClass {
static func handle(property: GenericClass<UITableViewCell>.SomeEnum) {}
}
Why do I get the compile error:
Cannot convert value of type 'GenericClass.SomeEnum' to expected
argument type 'GenericClass.SomeEnum'
Ofcourse, the fix would be adding the cast:
as! GenericClass<UITableViewCell>.SomeEnum
which results in this ugly code:
func callOtherClass() {
OtherClass.handle(property: enumProperty) as! GenericClass<UITableViewCell>.SomeEnum
}
But why do I need to cast? self is defined as GenericClass where T always is a UITableViewCell. The method handle expects that signature.
Is there any case this cast is needed because in some situations this will/can fail? I would not expect Swift just randomly asking me to insert a force cast. I expect Swift can just infer the types and sees it is safe, but somehow, Swift doesn't agree with me.
The problem here is that SomeEnum is actually GenericClass<T>.SomeEnum. There's no promise that T is exactly UITableViewCell, so it's not compatible with GenericClass<UITableViewCell> (generics are not covariant).
Typically in this case, what you want to do is move SomeEnum outside of GenericClass, since nothing about it is actually generic:
enum SomeEnum {
case myValue
}
class GenericClass<T: UITableViewCell> {
let enumProperty = SomeEnum.myValue
func callOtherClass() {
OtherClass.handle(property: enumProperty) // Compile error
}
}
class OtherClass {
static func handle(property: SomeEnum) {}
}
But if there's a reason for it to be generic, see Robert Dresler's answer, which is how you would specialize the function correctly:
class OtherClass {
static func handle<T: UITableViewCell>(property: GenericClass<T>.SomeEnum) {}
}
Make your static method also generic and create generic constrain for parameter inheriting from UITableViewCell. Then use this generic parameter in method parameter
class OtherClass {
static func handle<T: UITableViewCell>(property: GenericClass<T>.SomeEnum) {}
}

Swift: Return class constrained with generic type

I have a basic generic class:
class SharedClass<T> {}
And a builder for it:
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return ...
}
}
What I want to be able to do, is return an instance that inherits SharedClass, and conforms to T. For example:
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.doIt()
Unfortunately, the Swift compiler complains that the returned type does not have a member named doIt.
Is there a way to achieve what I'm looking for?
I suspect it's not so much that you want the returned class to be constrained by the generic type, as you're asking the returned class to be an instance of the constrained type. In your snippet, you're expecting the unwrapped result to conform to MyObject. Taking this a step further, it means that the conformance of SharedClass is determined entirely from how it was constructed. As far as I know this isn't supported in Swift.
However, there's nothing stopping you having a member of SharedClass that is a T. Something along the lines of:
class SharedClass<T> {
var v : T?
}
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return SharedClass()
}
}
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.v?.doIt()

Further constraining a generic function from a Swift Protocol

I have a Swift protocol defined like this:
protocol MyProtocol {
func genericMethod<T:MyProtocol>(param:T) -> ()
}
I can implement the generic method in a base class like this:
class MyBaseClass : MyProtocol {
func genericMethod<T where T:MyProtocol>(param:T) -> () {
println("Performing generic method for type \(T.self)")
}
}
class MySubClass : MyBaseClass {
...
}
So far, so good. I can implement this method and it compiles and runs just fine.
Now, I want to do something similar but in my base class I want to further constrain the type of the generic method by requiring it to conform with a protocol such as Comparable. I try this:
class MyBaseClass : MyProtocol {
func genericMethod<T where T:MyProtocol, T:Comparable>(param:T) -> () {
println("Performing generic method for type \(T.self)")
}
}
Once I add this additional constraint on type T, the class MyClass will not compile because it does not conform to the protocol anymore.
It seems like adding an additional constraint on a generic type should not cause it to cease conforming with a protocol. What am I missing? It seems to me that the protocol is saying that genericMethod must be passed a parameter of a type that conforms with MyProtocol. When I go to implement this in MyBaseClass - just one possible implementation of MyProtocol - that I should be able to restrict that implementation further by saying that the parameter myst conform with Comparable in addition to MyProtocol
Is there a way to refine a generic type in a base implementation like I'm trying to do here?
Adding the additional constraint on a generic type should cause it to cease conforming with the protocol because the protocol is supposed to guarantee conformance, and conformance cannot be guaranteed with subtypes that aren't Comparable. If you want all MyProtocol objects to conform to Comparable then you should make it part of the MyProtocol definition.
protocol MyProtocol: Comparable {
//...
}
I haven't tried this, but it might also work if you make MyBaseClass a Comparable type.
One solution is to go the other way - define your protocol's version of the generic as the most restrictive case. This compiles:
protocol P {
func genericMethod<T where T:P, T:Comparable>(param:T) -> ()
}
class C1 : P {
func genericMethod<T> (param:T) -> () {} // compiles even though omits Comparable
func test() {
genericMethod(C1()) // compiles even though C1 is not a Comparable
}
}

'Self' is only available in a protocol or as the result of a class method

Update: Swift 3 permits Self to be used from other types, thanks to SE-0068 – Expanding Swift Self to class members and value types.
You can return "Self" from a class function:
extension NSObject {
class func makeOne() -> Self {
return self()
}
}
So you can do:
let set : NSCountedSet = NSCountedSet.makeOne()
However, the following two don't compile:
extension NSObject {
class func makeTwo() -> (Self, Self) {
return (self(), self())
}
class func makeMany() -> [Self] {
return [self(), self(), self(), self(), self()]
}
}
The error is:
<REPL>:11:34: error: 'Self' is only available in a protocol or as the result of a class method; did you mean 'NSObject'?
class func makeTwo() -> (Self, Self) {
^~~~
NSObject
<REPL>:11:40: error: 'Self' is only available in a protocol or as the result of a class method; did you mean 'NSObject'?
class func makeTwo() -> (Self, Self) {
^~~~
NSObject
<REPL>:15:35: error: 'Self' is only available in a protocol or as the result of a class method; did you mean 'NSObject'?
class func makeMany() -> [Self] {
^~~~
NSObject
Does anyone know of any way to declare that a class function returns multiple instances of the class itself?
The problem, I suspect, is that Self is ambiguous; it means "this class or a subclass, whatever the thing happens to be at the time we are called". In other words, Self is polymorphic. But you can't make an array consisting of two different classes, for example. And although the class may permit a certain initializer, we cannot know in advance that its subclass will.
The solution is to use the class name itself. Here's an example for a struct Thing:
extension Thing {
static func makeTwo() -> (Thing, Thing) {
return (Thing(), Thing())
}
}

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