41

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.

exists-forall
  • 4,148
  • 4
  • 22
  • 29
  • 1
    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 Jun 03 '16 at 14:49

8 Answers8

19

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
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 1
    I like it, but in my case I was wanting to use the protocol `TransmogrifiableView` in another protocol, and then get "Protocol can only be used as a generic constraint because it has Self or associated type requirements" – Chris Prince Jul 04 '17 at 20:08
17

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

let myView: NSView & TransmogrifiableView

For more information, checkout issue #156 Subclass Existentials

Antoine
  • 23,526
  • 11
  • 88
  • 94
  • 1
    This is on defining the instance type, not on the protocol declaration, completely different things. – Firo Jan 10 '18 at 20:26
12

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

GrandFelix
  • 541
  • 5
  • 9
  • 1
    @Firo I have just tried and it works. "error: 'ProtocolToComform' requires that 'TestProtocolComfirmation' inherit from 'NewCls' class TestProtocolComfirmation: ProtocolToComform..." – GrandFelix Jan 11 '18 at 06:41
  • 1
    This doesn't work for us either. We have a protocol LedLevelSupplyable where Self: NSObject, and we want to call addObserver() on instances of this protocol https://developer.apple.com/documentation/objectivec/nsobject/1412787-addobserver but the compiler says Value of type 'LedLevelSupplyable?' has no member 'addObserver' Any idea what's wrong? – Kartick Vaddadi Mar 09 '18 at 12:54
  • 1
    @VaddadiKartick https://gist.github.com/GrandFelix/de6c854b17f45062cc8e1b5cf39b535f This works but did not try with addObserver etc. You must provide more code to determinate the problem. But I think that your problem is not the subject of this question I guess. – GrandFelix Mar 09 '18 at 13:09
  • This does not work as is TransmogrifiableView was NSView but I added `if let trans = TransmogrifiableView as NSView { do whatever you need with trans}` – Adriana Mar 27 '18 at 12:15
5

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

Community
  • 1
  • 1
Ramzi Khahil
  • 4,932
  • 4
  • 35
  • 69
  • This doesn't work, because it prevents any of my `TransmogrifiableView`'s from inheriting from descendants of `NSView`, like, for example, `NSBox` or `NSTextField`. – exists-forall Jan 07 '15 at 07:59
  • Thank you, those are all pretty good options. It would be nice if apple allowed protocols to "inherit" from classes, but the language is still growing, so who knows what we might have in the future. In the mean time, I think the extension approach is probably the best -- it sacrifices a little bit of type safety, but it's simple and it works. – exists-forall Jan 07 '15 at 08:48
  • 1
    Extensions worked out. I think, this syntax should be introduced to Swift: `protocol TransmogrifiableView: class, NSView` – Richard Topchii Oct 23 '15 at 09:03
3

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?

Fabio Poloni
  • 8,219
  • 5
  • 44
  • 74
3

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
Devin Pitcher
  • 2,562
  • 1
  • 18
  • 11
0

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

ghostatron
  • 2,620
  • 23
  • 27
0

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

JMiguel
  • 1,459
  • 1
  • 10
  • 12
  • Add the relevant content from the link to the body to your answer. – Grant Miller Jul 14 '18 at 00:00
  • it would be pratically a copy of the answer... and I got the previous attempt of answering here removed for being posting the same answer on both places – JMiguel Jul 14 '18 at 17:26