7

I have a class extension declared in another module (using pods) like this.

public extension UIView {
  open func doStuff() {...}
}

And when I try to override this method in subclass inside my current project module

class ConcreteView : UIView {
  override open func doStuff() {...}
}

I get an error:

Overriding non-open instance method outside of its defining module

despite the method is actually marked as open

As a workaround I declared another class inside same module where extension is declared and overrided desired method there

public class CustomView: UIView {
  override open func doStuff() {...}
}

and set this class as super class for my class in main module

class ConcreteView : CustomView

so only after this I was able to override the method.

It really looks like a bug in swift3 but maybe I've omitted some understanding of why it works in that way?

Mykola Denysyuk
  • 1,935
  • 1
  • 15
  • 15
  • 2
    This looks similar: http://stackoverflow.com/questions/39141975/swift-open-keyword-overridable-method-properties-in-extension. – Remove the `public` from `public extension UIView`. – Martin R Dec 05 '16 at 20:54
  • 1
    @MartinR yeap, it works - thanks! It would be great if you formulate the description why it works in this way into an answer so I can mark it or at least point to explanation somewhere in the web. – Mykola Denysyuk Dec 06 '16 at 09:19

1 Answers1

18

Short answer: The doStuff method in

public extension UIView {
  open func doStuff() {...}
}

has an effective access level "public" because the extension is marked public. Therefore it cannot be overridden in a subclass.

Note that Xcode warns

warning: declaring instance method in PUBLIC extension

and the warning text should be (see below)

warning: declaring OPEN instance method in PUBLIC extension

To solve the problem, remove the public access modifier of the extension:

extension UIView {
  open func doStuff() {...}
}

Now the extension has the access level "open" (inherited from open class UIView) and the effective access level of doStuff is "open" and it can be subclassed.

Longer answer:

Access Control in the Swift reference states that

... you can mark an extension with an explicit access-level modifier ... to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

but actually you can only restrict type members within the extension to the same or a lower access. Unfortunately, I could not find a definite reference for this fact in the documentation.

SE-0117 Allow distinguishing between public access and public overridability states that

For example, the true access level of a type member is computed as the minimum of the true access level of the type and the declared access level of the member. If the class is public but the member is open, the true access level is public.

but does not explain how this applies to extensions.

The check can be seen in the compiler source code TypeCheckAttr.cpp. If the access level of an item is larger then the access level of the containing extension then the diag::access_control_ext_member_more diagnostic message is emitted:

WARNING(access_control_ext_member_more,none,
    "declaring %select{PRIVATE|a fileprivate|an internal|a public}0 %1 in "
    "%select{a private|a fileprivate|an internal|PUBLIC}2 extension",
    (Accessibility, DescriptiveDeclKind, Accessibility))

Note that the "open" level is missing in the selection, and that is why it is missing in

warning: declaring instance method in PUBLIC extension

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Does this deserve filing a bug? – matt Dec 06 '16 at 19:13
  • @matt: Definitely for the incomplete diagnostic message. – It seems to be intentional that one can only *lower* access in an extension, so that could be a documentation bug (or I did not find it). – Martin R Dec 06 '16 at 19:22
  • Cool, thanks for doing that. I think your research into the source code makes this particularly cogent. – matt Dec 06 '16 at 21:13
  • thanks @MartinR for such complete answer! There is one thing from SE-0117 that sounds irrelevant regarding quote you've used: `Note that a class member may be explicitly declared open even if its class is not open or even public.` – Mykola Denysyuk Dec 07 '16 at 13:54
  • @MykolaDenysyuk: As I understand it, you can define a member with a higher (less strict) access level than the class, but it has no effect. What I don't know is why the compiler warns if you do that in an extension, but not in the class itself. – Martin R Dec 07 '16 at 15:18
  • @MartinR hmm, it make sense, not totally clear for me but understandable enough. Thanks again! – Mykola Denysyuk Dec 07 '16 at 16:27
  • Extensions cannot use 'open' as their default access; use 'public' - that what u get if u write *open extension* – Zaporozhchenko Oleksandr May 21 '19 at 08:21