I tend to only put the necessities (stored properties, initializers) into my class definitions and move everything else into their own extension
, kind of like an extension
per logical block that I would group with // MARK:
as well.
For a UIView subclass for example, I would end up with an extension for layout-related stuff, one for subscribing and handling events and so forth. In these extensions, I inevitably have to override some UIKit methods, e.g. layoutSubviews
. I never noticed any issues with this approach -- until today.
Take this class hierarchy for example:
public class C: NSObject {
public func method() { print("C") }
}
public class B: C {
}
extension B {
override public func method() { print("B") }
}
public class A: B {
}
extension A {
override public func method() { print("A") }
}
(A() as A).method()
(A() as B).method()
(A() as C).method()
The output is A B C
. That makes little sense to me. I read about Protocol Extensions being statically dispatched, but this ain't a protocol. This is a regular class, and I expect method calls to be dynamically dispatched at runtime. Clearly the call on C
should at least be dynamically dispatched and produce C
?
If I remove the inheritance from NSObject
and make C
a root class, the compiler complains saying declarations in extensions cannot override yet
, which I read about already. But how does having NSObject
as a root class change things?
Moving both overrides into their class declaration produces A A A
as expected, moving only B
's produces A B B
, moving only A
's produces C B C
, the last of which makes absolutely no sense to me: not even the one statically typed to A
produces the A
-output any more!
Adding the dynamic
keyword to the definition or an override does seem to give me the desired behavior 'from that point in the class hierarchy downwards'...
Let's change our example to something a little less constructed, what actually made me post this question:
public class B: UIView {
}
extension B {
override public func layoutSubviews() { print("B") }
}
public class A: B {
}
extension A {
override public func layoutSubviews() { print("A") }
}
(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()
We now get A B A
. Here I cannot make UIView's layoutSubviews dynamic by any means.
Moving both overrides into their class declaration gets us A A A
again, only A's or only B's still gets us A B A
. dynamic
again solves my problems.
In theory I could add dynamic
to all override
s I ever do but I feel like I'm doing something else wrong here.
Is it really wrong to use extension
s for grouping code like I do?
Extensions cannot/should not override.
It is not possible to override functionality (like properties or methods) in extensions as documented in Apple's Swift Guide.
Extensions can add new functionality to a type, but they cannot override existing functionality.
The compiler is allowing you to override in the extension for compatibility with Objective-C. But it's actually violating the language directive.
That reminded me of Isaac Asimov's "Three Laws of Robotics" 🤖
Extensions (syntactic sugar) define independent methods that receive their own arguments. The function that is called for i.e. layoutSubviews
depends on the context the compiler knows about when the code is compiled. UIView inherits from UIResponder which inherits from NSObject so the override in the extension is permitted but should not be.
So there's nothing wrong with grouping but you should override in the class not in the extension.
Directive Notes
You can only override
a superclass method i.e. load()
initialize()
in an extension of a subclass if the method is Objective-C compatible.
Therefore we can take a look at why it is allowing you to compile using layoutSubviews
.
All Swift apps execute inside the Objective-C runtime except for when using pure Swift-only frameworks which allow for a Swift-only runtime.
As we found out the Objective-C runtime generally calls two class main methods load()
and initialize()
automatically when initializing classes in your app’s processes.
dynamic
modifierFrom the Apple Developer Library (archive.org)
You can use the dynamic
modifier to require that access to members be dynamically dispatched through the Objective-C runtime.
When Swift APIs are imported by the Objective-C runtime, there are no guarantees of dynamic dispatch for properties, methods, subscripts, or initializers. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. 😳
So dynamic
can be applied to your layoutSubviews
-> UIView Class
since it’s represented by Objective-C and access to that member is always used using the Objective-C runtime.
That's why the compiler allowing you to use override
and dynamic
.