- Proposal: SE-NNNN
- Author: Evan Maloney
- Status: Draft
- Review manager: TBD
Swift protocol
s are useful for the declaring interfaces that must be provided by conforming entities (in other words: struct
s, class
es and enum
s). Adopters of a given protocol are free to provide any implementation they wish, so long as it supplies the necessary interfaces.
Separating interface from implementation is widely considered to be a best practice in software design, and the Swift protocol
is designed for this use-case.
Unfortunately, the protocol
does not cover all of the cases where a developer might want to specify an interface to be implemented by another entity.
For example, consider the class
, which allows the creation of an inheritance hierarchy. Often, a class
in a hierarchy exists merely to provide a common implementation to subclasses. Such classes aren't ever intended to be instantiated directly; only subclasses will be instantiated.
To illustrate the point, imagine a view controller class that:
- Places an animating
UIActivityIndicatorView
onscreen - Performs some operation to retrieve some text
- Puts the text in a
UITextView
and places it onscreen - Hides the
UIActivityIndicatorView
Now imagine you had many cases in your application where you could benefit from such a view controller, and each case differed only in the operation required to retrieve the text (represented by Step 2 above).
Ideally, you would be able to achieve this by declaring the interface for a function without needing to specify an implementation, the same way you would with a protocol:
func retrieveText() -> String
In other languages, such as C++, this concept exists in the form of an abstract class. However, Swift does not support this, so developers are forced to provide useless implementations such as:
func retrieveText() -> String
{
fatalError("Subclasses must implement retrieveText()")
}
The idea here is that subclasses should always provide a retrieveText()
implementation, and therefore the call to fatalError()
should never be hit.
This has a few significant downsides:
-
It forces the developer to write code that should never be executed under normal conditions. This seems like a waste.
-
Because a default implementation is provided--the one that calls
fatalError()
--the compiler has no way of knowing that the subclasses are supposed to provide an implementation, too. -
If a subclass implementor forgets to provide a
retrieveText()
function, the error will not be caught until runtime, and not until a user navigates to the affected portion of the application. This may not occur until the application has shipped.
The proposed solution involves adding support for abstract classes to Swift.
This would entail:
-
Allowing functions and properties to be declared
abstract
. An abstract function or property declares the interface without specifying the implementation. -
Allowing abstract classes to be defined by partially unimplemented protocol conformances. If a class declares conformance to a protocol without providing an implementation for each of that protocol's properties and functions, it is an abstract class.
-
Requiring classes to be explicitly declared as
abstract
if it has one or more unimplemented functions or properties.
Functions can be declared abstract using the abstract
keyword, which must appear before the func
keyword in the declaration. Otherwise, the notation is identical to how the function would be declared if it were to appear in a protocol:
public abstract func retrieveText() -> String
As long as the abstract
keyword appears before the func
, the order of appearance of the abstract
keyword relative to any public
, private
or internal
access modifiers is not meaningful.
The following declaration is equivalent to the one above:
abstract public func retrieveText() -> String
Abstract property declarations are identical to what would be found in a protocol, but are prefixed with the abstract
keyword, which must appear first:
abstract var fileName: String { get }
abstract var favoriteColor: UIColor { get set }
As is typical with protocol declarations, var
is always used and not let
.
A class can be made abstract by declaring conformance to a protocol that it does not implement fully.
For example, say you had a protocol Vehicle
:
protocol Vehicle
{
var name: String { get }
var color: UIColor { get }
var numberOfWheels: Int { get }
var isParked: Bool { get set }
func driveTo(destination: Location) throws
}
In your code, you're able to factor out everything except the driveTo()
function, the implementation of which is vehicle-specific. The common code goes into a BaseVehicle
class:
abstract class BaseVehicle: Vehicle
{
let name: String
let color: UIColor
let numberOfWheels: Int
var isParked: Bool
init(name: String, color: UIColor, numberOfWheels: Int, isParked: Bool = true)
{
self.name = name
self.color = color
self.numberOfWheels = numberOfWheels
self.isParked = isParked
}
}
The BaseVehicle
class partially conforms to the Vehicle
protocol: the name
, color
, numberOfWheels
and isParked
properties are provided, but the driveTo()
function remains unimplemented.
As a result, BaseVehicle
is an abstract class and must be declared as such.
A class
must be declared as abstract
if any of the following are true:
- If the class declares one or more
abstract
functions or properties - If the class declares conformance to a
protocol
but does not supply implementations for every one of the functions and properties declared in that protocol. - If the class inherits from an
abstract class
and does not supply an implementation for every one of the unimplemented functions or properties.
Classes are marked as abstract by placing the abstract
keyword before the class
keyword at the top of the class declaration, eg.:
public abstract class MyAbstractClass
{
// ...code...
}
As long as the abstract
keyword appears before the class
, the order of appearance of the abstract
keyword relative to any public
, private
or internal
access modifiers is not meaningful.
The following declaration is equivalent to the one above:
abstract public class MyAbstractClass
{
// ...code...
}
Because an abstract class is not a complete implementation, the compiler will not allow instantiation of abstract classes.
Attempting to instantiate an abstract class will result in a compiler error.
None, since this does not affect any existing constructs. Implementation of this proposal will not result in any code breakage.
This idea has been discussed in the following swift-evolution mailing list threads: