Swift -- Require classes implementing protocol to be subclasses of a certain class

19,529

Solution 1

I think you are after a subclass of NSView. Try this:

protocol TransmogrifiableView {
    func transmogrify()
}

class MyNSView: NSView, TransmogrifiableView {
    // do stuff.
}

And later in the code accept objects of type MyNSView.

Edit

You maybe want an Extension, see this

extension NSView: TransmogrifiableView {
    // implementation of protocol requirements goes here
}
  • Note that you will not be able to get an NSView without this extra method.
  • You can separately extend subclasses of NSView to override this new method.

Yet another option is to make a class which holds a pointer to an NSView, and implements additional methods. This will also force you to proxy all methods from NSView that you want to use.

class NSViewWrapper: TransmogrifiableView {
    var view : NSView!
    // init with the view required.
    //  implementation of protocol requirements goes here.
    .....
   // proxy all methods from NSView.
   func getSuperView(){
       return self.view.superView
   }
}

This is quite long and not nice, but will work. I would recommend you to use this only if you really cannot work with extensions (because you need NSViews without the extra method).

Solution 2

Update. In the latest Swift version you can just write

protocol TransmogrifiableView: NSView {
    func transmogrify()
}

, and this will enforce the conformer types to be either NSView, or a subclass of it. This means the compiler will "see" all members of NSView.


Original answer

There is a workaround by using associated types to enforce the subclass:

protocol TransmogrifiableView {
    associatedtype View: NSView = Self
    func transmogrify()
}

class MyView: NSView, TransmogrifiableView { ... } // compiles
class MyOtherClass: TransmogrifiableView { ... } // doesn't compile

Solution 3

Starting from Swift 4 you can now define this as followed:

let myView: NSView & TransmogrifiableView

For more information, checkout issue #156 Subclass Existentials

Solution 4

You could use something like this:

protocol TransmogrifiableView where Self:NSView {}

This requires all created instances which one conforms to TransmogrifiableView protocol to be subclassed with NSView

Solution 5

As per definition a protocol just declares requirements of "methods, properties an other requirements". And by "other requirements" it means a superclass is not a part of it.

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

Right now, I don't see a clean solution. It's possible to use a where-clause to define a type which is a NSView and conforms to the TransmogrifiableView like this:

class MyClass<T where T: NSView, T: TransmogrifiableView> {
    var aTransmogrifiableNSView: T
}

Or you could use another superclass:

protocol TransmogrifiableViewProtocol {
    func transmogrify()
}

class TransmogrifiableView: NSView, TransmogrifiableViewProtocol {
    func transmogrify() {
        assert(false, "transmogrify() must be overwritten!")
    }
}

class AnImplementedTransmogrifiableView: TransmogrifiableView {
    func transmogrify() {
        println("Do the transmogrification...")
    }
}

In the end both solutions aren't clean and wouldn't satisfy myself. Maybe an abstract-keyword will be added to Swift someday?

Share:
19,529
exists-forall
Author by

exists-forall

Student of Computer Science and Mathematics at UC Berkeley.

Updated on June 02, 2022

Comments

  • exists-forall
    exists-forall about 2 years

    I'm creating several NSView classes, all of which support a special operation, which we'll call transmogrify. At first glance, this seems like the perfect place for a protocol:

    protocol TransmogrifiableView {
        func transmogrify()
    }
    

    However, this protocol does not enforce that every TransmogrifiableView be an NSView as well. This means that any NSView methods I call on a TransmogrifiableView will not type check:

    let myView: TransmogrifiableView = getTransmogrifiableView()
    let theSuperView = myView.superView // error: TransmogrifiableView does not have a property called 'superview'
    

    I don't know how to require that all classes implementing my protocol are also subclasses of NSView. I tried this:

    protocol TransmogrifiableView: NSView {
        func transmogrify()
    }
    

    but Swift complains that protocols cannot inherit from classes. It does not help to turn the protocol into a class-only protocol using

    protocol TransmogrifiableView: class, NSView {
        func transmogrify()
    }
    

    I cannot make TransmogrifiableView a superclass rather than a protocol, because some of my TransmogrifiableView classes must be subclasses of other, non-transmogrifiable views.

    How should I require that all TransmogrifiableView's also be NSView's? I really don't want to pepper my code with "as" conversions, which are bad form and distracting.