When was the last time you subclassed a class you created natively in Swift that wasn’t part of the Cocoa system? How often do you find yourself subclassing non-Cocoa types now that protocol extensions and regular ordinary extension extensions exist?
If your answer is somewhere between 0% and 5%, you’re fairly typical. Reference types are no longer tightly coupled with inheritance under Swift’s type system.
Pushing further, how often do you create classes and subclasses with the intention that they be furthered subclassed by API clients outside a particular module? (Assuming, of course, you’re not Apple, and you’re not writing views and controllers…)
Update: Matthew Johnson asked that I run this survey. Thank you for your participation.
When subclassing becomes the exception rather than the rule, is it time to consider making classes final
by default? Or would it be better to seal modules as internal-by-default
so public classes cannot be subclassed outside their module of origin.
Debate is currently raging on the Swift evolution list about how this might be approached, whether it should be done at all, and if classes could be designed to force calls to superclasses for overridden methods (“requires super call”).
John McCall writes:
Our current intent is that public subclassing and overriding will be locked down by default, but internal subclassing and overriding will not be. I believe that this strikes the right balance, and moreover that it is consistent with the general language approach to code evolution, which is to promote “consequence-free” rapid development by:
(1) avoiding artificial bookkeeping obstacles while you’re hacking up the initial implementation of a module, but
(2) not letting that initial implementation make implicit source and binary compatibility promises to code outside of the module and
(3) providing good language tools for incrementally building those initial prototype interfaces into stronger internal abstractions.
All the hard limitations in the defaults are tied to the module boundary because we assume that it’s straightforward to fix any problems within the module if/when you decided you made a mistake earlier.
So, okay, a class is subclassable by default, and it wasn’t really designed for that, and now there are subclasses in the module which are causing problems. As long as nobody’s changed the default (which they could have done carelessly in either case, but are much less likely to do if it’s only necessary to make an external subclass), all of those subclasses will still be within the module, and you still have free rein to correct that initial design mistake.
A thought from Joe Groff:
Robust subclassability requires conscious design just like all other aspects of API design.
Updated to add from Jordan Rose:
The interesting thing about this is that the “error of omission”—of failing to think about whether a class should be final—is worse than the alternative. Ignoring optimizations for a minute, a class that starts out ‘final’ can certainly become non-final later; it doesn’t change how the class is currently used.* For a lot of library evolution questions, this is the preferred answer: the default should be safe, and the designer of the class can choose to be more aggressive later.
One Comment
Thanks for calling attention to this Erica! I think it’s a really important discussion.
What do you think about adding a poll asking people to estimate the percentage of the classes they write are intended to be superclasses? This is the most important question in my mind. Having some data from the app development community might be helpful (even if it is totally unscientific).
Whether or not the class is itself a subclass of a framework class isn’t really relevant so it would be important to make that clear.