blob: a90036ae1cb86296f8c57cb39ab4072458f0382a [file] [log] [blame]
:orphan:
Initialization
==============
.. contents::
Superclass Delegation
---------------------
The initializer for a class that has a superclass must ensure that its
superclass subobject gets initialized. The typical way to do so is
through the use of superclass delegation::
class A {
var x: Int
init(x: Int) {
self.x = x
}
}
class B : A {
var value: String
init() {
value = "Hello"
super.init(5) // superclass delegation
}
}
Swift implements two-phase initialization, which requires that all of
the instance variables of the subclass be initialized (either within
the class or within the initializer) before delegating to the
superclass initializer with ``super.init``.
If the superclass is a Swift class, superclass delegation is a direct
call to the named initializer in the superclass. If the superclass is
an Objective-C class, superclass delegation uses dynamic dispatch via
``objc_msgSendSuper`` (and its variants).
Peer Delegation
---------------
An initializer can delegate to one of its peer initializers, which
then takes responsibility for initializing this subobject and any
superclass subobjects::
extension A {
init fromString(s: String) {
self.init(Int(s)) // peer delegation to init(Int)
}
}
One cannot access any of the instance variables of ``self`` nor invoke
any instance methods on ``self`` before delegating to the peer
initializer, because the object has not yet been
constructed. Additionally, one cannot initialize the instance
variables of ``self``, prior to delegating to the peer, because doing
so would lead to double initializations.
Peer delegation is always a direct call to the named initializer, and
always calls an initializer defined for the same type as the
delegating initializer. Despite the syntactic similarities, this is
very different from Objective-C's ``[self init...]``, which can call
methods in either the superclass or subclass.
Peer delegation is primarily useful when providing convenience
initializers without having to duplicate initialization code. However,
peer delegation is also the only viable way to implement an
initializer for a type within an extension that resides in a different
resilience domain than the definition of the type itself. For example,
consider the following extension of ``A``::
extension A {
init(i: Int, j: Int) {
x = i + j // initialize x
}
}
If this extension is in a different resilience domain than the
definition of ``A``, there is no way to ensure that this initializer
is initializing all of the instance variables of ``A``: new instance
variables could be added to ``A`` in a future version (these would not
be properly initialized) and existing instance variables could become
computed properties (these would be initialized when they shouldn't
be).
Initializer Inheritance
-----------------------
Initializers are *not* inherited by default. Each subclass takes
responsibility for its own initialization by declaring the
initializers one can use to create it. To make a superclass's
initializer available in a subclass, one can re-declare the
initializer and then use superclass delegation to call it::
class C : A {
var value = "Hello"
init(x: Int) {
super.init(x) // superclass delegation
}
}
Although ``C``'s initializer has the same parameters as ``A``'s
initializer, it does not "override" ``A``'s initializer because there
is no dynamic dispatch for initializers (but see below).
We could syntax-optimize initializer inheritance if this becomes
onerous. DaveA provides a reasonable suggestion::
class C : A {
var value = "Hello"
@inherit init(Int)
}
*Note*: one can only inherit an initializer into a class ``C`` if all
of the instance variables in that class have in-class initializers.
Virtual Initializers
--------------------
The initializer model above only safely permits initialization when we
statically know the type of the complete object being initialized. For
example, this permits the construction ``A(5)`` but not the
following::
func createAnA(_ aClass: A.metatype) -> A {
return aClass(5) // error: no complete initializer accepting an ``Int``
}
The issue here is that, while ``A`` has an initializer accepting an
``Int``, it's not guaranteed that an arbitrary subclass of ``A`` will
have such an initializer. Even if we had that guarantee, there
wouldn't necessarily be any way to call the initializer, because (as
noted above), there is no dynamic dispatch for initializers.
This is an unacceptable limitation for a few reasons. The most obvious
reason is that ``NSCoding`` depends on dynamic dispatch to
``-initWithCoder:`` to deserialize an object of a class type that is
dynamically determined, and Swift classes must safely support this
paradigm. To address this limitation, we can add the ``virtual``
attribute to turn an initializer into a virtual initializer::
class A {
@virtual init(x: Int) { ... }
}
Virtual initializers can be invoked when constructing an object using
an arbitrary value of metatype type (as in the ``createAnA`` example
above), using dynamic dispatch. Therefore, we need to ensure that a
virtual initializer is always a complete object initializer, which
requires that every subclass provide a definition for each virtual
initializer defined in its superclass. For example, the following
class definition would be ill-formed::
class D : A {
var floating: Double
}
because ``D`` does not provide an initializer accepting an ``Int``. To
address this issue, one would add::
class D : A {
var floating: Double
@virtual init(x: Int) {
floating = 3.14159
super.init(x)
}
}
As a convenience, the compiler could synthesize virtual initializer
definitions when all of the instance variables in the subclass have
in-class initializers::
class D2 : A {
var floating = 3.14159
/* compiler-synthesized */
@virtual init(x: Int) {
super.init(x)
}
}
This looks a lot like inherited initializers, and can eliminate some
boilerplate for simple subclasses. The primary downside is that the
synthesized implementation might not be the right one, e.g., it will
almost surely be wrong for an inherited ``-initWithCoder:``. I don't
think this is worth doing.
*Note*: as a somewhat unfortunate side effect of the terminology, the
initializers for structs and enums are considered to be virtual,
because they are guaranteed to be complete object initializers. If
this bothers us, we could use the term (and attribute) "complete"
instead of "virtual". I'd prefer to stick with "virtual" and accept
the corner case.
Initializers in Protocols
-------------------------
We currently ban initializers in protocols because we didn't have an
implementation model for them. Protocols, whether used via generics or
via existentials, use dynamic dispatch through the witness table. More
importantly, one of the important aspects of protocols is that, when a
given class ``A`` conforms to a protocol ``P``, all of the subclasses
of ``A`` also conform to ``P``. This property interacts directly with
initializers::
protocol NSCoding {
init withCoder(coder: NSCoder)
}
class A : NSCoding {
init withCoder(coder: NSCoder) { /* ... */ }
}
class B : A {
// conforms to NSCoding?
}
Here, ``A`` appears to conform to ``NSCoding`` because it provides a
matching initializer. ``B`` should conform to ``NSCoding``, because it
should inherit its conformance from ``A``, but the lack of an
``initWithCoder:`` initializer causes problems. The fix here is to
require that the witness be a virtual initializer, which guarantees
that all of the subclasses will have the same initializer. Thus, the
definition of ``A`` above will be ill-formed unless ``initWithCoder:``
is made virtual::
protocol NSCoding {
init withCoder(coder: NSCoder)
}
class A : NSCoding {
@virtual init withCoder(coder: NSCoder) { /* ... */ }
}
class B : A {
// either error (due to missing initWithCoder) or synthesized initWithCoder:
}
As noted earlier, the initializers of structs and enums are considered
virtual.
Objective-C Interoperability
----------------------------
The initialization model described above guarantees that objects are
properly initialized before they are used, covering all of the major
use cases for initialization while maintaining soundness. Objective-C
has a very different initialization model with which Swift must
interoperate.
Objective-C Entrypoints
~~~~~~~~~~~~~~~~~~~~~~~
Each Swift initializer definition produces a corresponding Objective-C
init method. The existence of this init method allows object
construction from Objective-C (both directly via ``[[A alloc]
init:5]`` and indirectly via, e.g., ``[obj initWithCoder:coder]``)
and initialization of the superclass subobject when an Objective-C class
inherits from a Swift class (e.g., ``[super initWithCoder:coder]``).
Note that, while Swift's initializers are not inherited and cannot
override, this is only true *in Swift code*. If a subclass defines an
initializer with the same Objective-C selector as an initializer in
its superclass, the Objective-C init method produced for the former
will override the Objective-C init method produced for the
latter.
Objective-C Restrictions
~~~~~~~~~~~~~~~~~~~~~~~~
The emission of Objective-C init methods for Swift initializers open
up a few soundness problems, illustrated here::
@interface A
@end
@implementation A
- init {
return [self initWithInt:5];
}
- initWithInt:(int)x {
// initialize me
}
- initWithString:(NSString *)s {
// initialize me
}
@end
class B1 : A {
var dict: NSDictionary
init withInt(x: Int) {
dict = []
super.init() // loops forever, initializing dict repeatedly
}
}
class B2 : A {
}
@interface C : B2
@end
@implementation C
@end
void getCFromString(NSString *str) {
return [C initWithString:str]; // doesn't initialize B's dict ivar
}
The first problem, with ``B1``, comes from ``A``'s dispatched
delegation to ``-initWithInt:``, which is overridden by ``B1``'s
initializer with the same selector. We can address this problem by
enforcing that superclass delegation to an Objective-C superclass
initializer refer to a designated initializer of that superclass when
that class has at least one initializer marked as a designated
initializer.
The second problem, with ``C``, comes from Objective-C's implicit
inheritance of initializers. We can address this problem by specifying
that init methods in Objective-C are never visible through Swift
classes, making the message send ``[C initWithString:str]``
ill-formed. This is a relatively small Clang-side change.
Remaining Soundness Holes
~~~~~~~~~~~~~~~~~~~~~~~~~
Neither of the above "fixes" are complete. The first depends entirely
on the adoption of a not-yet-implemented Clang attribute to mark the
designated initializers for Objective-C classes, while the second is
(almost trivially) defeated by passing the ``-initWithString:``
message to an object of type ``id`` or using some other dynamic
reflection.
If we want to close these holes tighter, we could stop emitting
Objective-C init methods for Swift initializers. Instead, we would
fake the init method declarations when importing Swift modules into
Clang, and teach Clang's CodeGen to emit calls directly to the Swift
initializers. It would still not be perfect (e.g., some variant of the
problem with ``C`` would persist), but it would be closer. I suspect
that this is far more work than it is worth, and that the "fixes"
described above are sufficient.