Can I use a protocol to provide View init methods?

protocols

#21

I guess the problem I'm trying to workaround could actually be seen as bad design in NSView.

it would be nice if NSView something like viewIsReady() providing a common override point for both initialisation paths.

I suspect the odds of a new NSView method any time soon are pretty slim though, and I suppose there is some cost to adding an extra function call on every view...


(Jon Shier) #22

I think there's a been a bit of talk about two-phase initialization which could be used to paper over these issues, but it hasn't come up in a while.

What I'd really like to see is a new, Swift native UI framework that embraces the language, hopefully developed in the open (but I'd take a closed library too). I'd hope something like that is under development inside Apple and just waiting for ABI stability to go public.


(Goffredo Marocchi) #23

UI wise I am glad it is based on a dynamic framework like Objective-C... how many pleasant to use and productive (I.e. fast iteration with sane tooling) have you seen in statically and strongly typed languages?


(Slava Pestov) #24

You cannot use 'super' in a protocol extension because the method is not associated with a concrete class and it's not clear what superclass you would refer to. Would it be the upper bound (NSView in @ConfusedVorlon's example)?


#25

@Slava_Pestov surely super would just follow the normal inheritance chain?

so in a chain CustomView -> NSView -> NSResponder .... -> SomeRootObject

the extension method would say
what am I? I'm a CustomView! In that case, my super is NSView

Of course - it gets problematic in the following
what am I? I'm a SomeRootObject! In that case, my super is ???

so I can see the need for a restriction where an extension on a rootClass (just as Swift has now for non-extensions)
'super' members cannot be referenced in a root class

re 'the method is not associated with a concrete class';
In my example, the extension is limited to things that are NSViews, so we do 'know' that there is always a super.


(Slava Pestov) #26

Yeah, something like that might work. @jrose might have additional feedback.

Do you want to file an enhancement request on JIRA or write up an evolution proposal pitch?


#27

@jrose - anything to suggest here?

I'm happy to copy this suggestion to JIRA if that would be worthwhile


(Jordan Rose) #28

I really don't think this is possible in the general case. Because initializers aren't always inherited in Swift, you can only call initializers that are in some protocol (or are required). You can't assume that super.init(coder:) or super.init(frame:) exists if you're several levels down in the subclass hierarchy. (And as you noted, doing anything with super requires knowing that your superclass (1) exists, and (2) is itself a subclass of NSView, or whichever the interesting type is.)

In the general case, I can think of two other solutions. The first is, of course, to make a subclass of NSView instead of a protocol, and then have all your custom views inherit from that. That doesn't work if you want to be a custom NSButton or NSTextField, though.

The second is something like this...

protocol CommonInitView where Self: NSView {
  func commonInit()
}

extension NSView where Self: CommonInitView {
  /*@nonobjc*/ convenience init(commonFrame frameRect: NSRect) {
    self.init(frame: frameRect)
    commonInit()
  }
  /*@nonobjc*/ convenience init(commonCoder decoder: NSCoder) {
    self.init(code: decoder)
    commonInit()
  }
}

…but that only works if you control the initialization call, which you usually don't for NSView subclasses (that init(commonCoder:) is pretty glaringly not going to be called by NSKeyedUnarchiver).

I hate to say it, but I don't think there's a design here that's statically safe and does what you want without modifying AppKit, especially without initializer inheritance.


#29

@jrose - thanks for the response. I get that there are problems with my original request.

We had wandered on to talking about allowing extensions to access super in more limited cases though
I guess taking from your comments, that would be something like

  • extension is limited to a non-root class
  • calling a method where one of:
    • non-init method exists in the class being extended
    • method exists in the protocol
    • method is a required initialiser

any thoughts on that?


(Jordan Rose) #30

It's not just "extension is limited to a non-root class". What you really need is something like

extension CustomViewProto where Self: NSView, Self != NSView {
  …
}

because if Self is NSView, then super won't have, say, a superview property.

Stepping back, though, what do you plan to use this feature for? The only thing I can think of is providing overrides of existing methods across multiple superclasses (which, admittedly, can be useful in a class hierarchy like NSView's), but that's yet another feature on top of this, since normally you can't override methods in an extension unless the methods are @objc (which, as established, can't be provided in a protocol today).

Note that in this case, I think super has to refer to "the superclass of the type that originally conforms to the protocol", not "the superclass of type(of: self)".


#31

I didn't know you could specify conformance like that!

to be honest - I don't have a strong use case here (other than the one that can't be provided). It is more that we hit a limitation that doesn't seem necessary!

in terms of super existing - surely the only requirement is 'extension is limited to non-root class'.
surely if we're a non-root class - then super must exist.


(Jordan Rose) #32

Ah, you can't at the moment. I'm just saying what we'd need to add. (I think this steers clear of the normal problems with != constraints because of the : constraint, but I haven't thought it through.)

The problem isn't whether super exists, it's whether super has an implementation of the method/property/subscript/initializer we're trying to invoke.


#33

isn't that exactly the same problem as for non-extensions?

extension Foo where Self : NSView {

  func foo() {
    // error: 'super' cannot be used outside of class members*
    super.someMethod
  }

} 

In this case, then super.someMethod is available when NSResponder (superclass of NSView) has that method.

class Foo: NSView {

  func foo() {
    //the compiler knows whether this is ok or not
    super.someMethod
  }

}

(Jordan Rose) #34

The difference is that not only is Self an NSView, but its superclass is as well. In the extension-of-protocol-with-constraint case, you know that Self is an NSView, but you don't know that the superclass is, so you'd only be able to call NSResponder things using super.


#35

I'm not following what the difference is.

In both cases, super is NSResponder - so you can only call super.SomeNSResponderMethod.
I'm not getting hung up on NSView here - I'm talking about the general case

extension Foo where Self : SomeClassWhichIsNotARootClass {

  func foo() {
    // error: 'super' cannot be used outside of class members*
    super.someMethod
  }

} 

this seems like it should at least in theory be possible where SomeClassWhichIsNotARootClass.super has the method someMethod

btw - if the response is 'yes we could do that, but we don't want to - or don't see the point', then there is no argument from me!


(Jordan Rose) #36

If you say class Foo: NSView, then you know that super is an NSView.

If you say extension NSView, then you only know that super is an NSResponder. It might be an NSView at run time (if you call your method on an NSButton, for example), but it might just be a plain NSView.

extension MyProto where Self: NSView is more like the second case than the first.


I think you're correct that you could use this syntax to call methods on NSResponder (that was my misunderstanding), but I don't think that's useful. We already had trouble thinking of uses for the general feature, but if I can only call methods on the superclass then there's no way to add a method to every instance of this class, which means it can't be used to provide default implementations the way you originally wanted.

So yeah, maybe this is a "this could be allowed, but it would probably make this more confusing, not less". (There're also some philosophical arguments against allowing arbitrary super calls to anything but the method you're overriding, excepting initializer chaining.)


#37

got it - yes, I wasn't quite comparing apples to apples.

I was thinking about calling NSResponder methods though. Fair enough that this doesn't seem particularly necessary, and thanks for taking the time to clarify things along the thread.