39
votes

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.

8
My partial solution here was basically as mentioned in OP's last paragraph: I want the class to be a UIResponder. So, where the delegate is used, guard ... guard let r = delegate as? UIResponder ... I didn't find it too distracting. Great question, I was wondering same. - Fattie

8 Answers

18
votes

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
17
votes

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

let myView: NSView & TransmogrifiableView

For more information, checkout issue #156 Subclass Existentials

12
votes

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

5
votes

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

3
votes

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?

3
votes

For Swift 4, based on @Antoine's keen insight:

Create a protocol then use a typealias to give a cleaner name to a type that conforms to both a class and the protocol.

protocol Transmogrifiable {
    func transmogrify()
}
typealias TransmogrifiableView = NSView & Transmogrifiable

You can then define a class that inherits from that type....

class ATransmogView: TransmogrifiableView {
    func transmogrify() {
        print("I'm transmogging")
    }
}

....or define a class that inherits from the protocol and a subclass

// this also qualifies as a TransmogrifiableView

class BTransmogView: NSTextView, Transmogrifiable {
    func transmogrify() {
        print("I'm transmogging too")
    }
}

Now you can do this.

func getTransmogrifiableView() -> TransmogrifiableView {
    return someBool ? ATransmogView() : BTransmogView()
}

And this now compiles.

let myView: TransmogrifiableView = getTransmogrifiableView()
let theSuperView = myView.superView
0
votes

Still not the ideal solution, but here's a pattern I use on occasion:

  1. Define a protocol with the methods that you want to enforce.
  2. Define your base class, implementing whatever you want the children to get for free.
  3. In that base class (from #2), have an optional "delegate" variable of the protocol you made in #1.
  4. Define all children classes so that they inherit the base class and implement the protocol.

This let's your base class call the protocol methods while forcing the children to implement them (eg. self.myDelegate?.myProtocolMethod).

0
votes

check this solution here... Swift - class method which must be overridden by subclass

This solution allows to inherit from a class and also to have the protocol's compile time check. :)