File: ClassConstruction.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (163 lines) | stat: -rw-r--r-- 6,134 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
:orphan:

=================================================
 Integrating Swift Constructors with Objective-C
=================================================

.. warning:: This proposal was rejected, though it helped in the design of the
  final Swift 1 initialization model.

Objective-C's "designated initializers pattern seems at first to
create a great deal of complication.  However, designated initializers
are simply the only sound response to Objective-C's initialization rules,
which are the root cause of the complication.

This proposal suggests an approach to initialization that avoids the
problems inherent in Objective-C while still *allowing* Objective-C
programmers to pursue the designated initializer pattern on subclasses
of Swift classes.

The Root of the Problem
=======================

The root problem with Objective-C's initialization rules is that the
``init`` methods of a superclass automatically become public members
of its subclasses.  This leads to a soundness problem:

.. parsed-literal::

  @interface SuperClass
  - initSuperClass
  @end

  @interface Subclass : Superclass
  - (void)subclassMethod
  @end

  @implementation Subclass : Superclass
  char\* **name**\ ;                      // never initialized

  - (void)print { printf(\ **name**\ ); } // oops
  @end

  mySubclassInstance = [[Subclass alloc] initSuperClass]

Because there is no way to hide a superclass' ``init`` method from
clients, ensuring that subclass instances are properly initialized
requires overriding *every* superclass initializer in *every*
subclass:

.. parsed-literal::

  @implementation Subclass : Superclass
  char\* name;
  - initSuperClass {
    [super initSuperClass];       // Don't forget the superclass
    **name = "Tino";**
  }
  - (void)print { printf(name); } // OK
  @end

Following this rule is obviously tedious and error-prone for users.
Initialization is crucial to correctness, because it is where
invariants are established.  It therefore should be no more complex
than everything else to reason about.

Also, it means adding an ``init`` method in a base class can be
API-breaking.

Furthermore, as John McCall pointed out recently, it forces
inappropriate interfaces on subclasses.  For example, every subclass
of ``NSObject`` has a parameter-less ``init`` function, whether or not
there's an appropriate way to construct instances of that subclass
without parameters.  As a result, class designers may be forced to
expose weaker invariants than the ones they could otherwise establish.

Exceptions to the Rule
======================

I exaggerated a little in the previous section: because overriding
*every* superclass initializer in *every* subclass is so tedious, the
Objective-C community has identified some situations where you don't
really need to override every ``init`` method:

1. When you know the default zero-initialization of a class' instance
   variables is good enough, you don't need to override any ``init``
   methods from your superclass.

2. If a given superclass' ``init`` method always calls another
   ``init`` method, you don't need to override the first ``init``
   method because your instance variables will be initialized by your
   override of the second ``init`` method.  In this case, the first
   (outer) ``init`` method is called a **secondary initializer**.  Any
   ``init`` method that's not secondary is called a **designated
   initializer**.

How to Think About This
=======================

At this point I'll make a few assertions that I hope will be
self-evident, given the foregoing context:

1. If the programmer follows all the rules correctly, one initializer
   is as good as another: every ``init`` method, whether designated or
   secondary, fully initializes all the instance variables.  This is
   true for all clients of the class, including subclassers.

2. Distinguishing designated from secondary initializers does nothing
   to provide soundness.  It's *merely* a technique for limiting the
   tedious ``init`` method overrides required of users.

3. Swift users would not be well-served by a construction model that
   exposes superclass ``init`` methods to clients of subclasses by
   default.

Proposal
========

I suggest we define Swift initialization to be as simple and
easily-understood as possible, and avoid "interesting" interactions
with the more complicated Objective-C initialization process.  If we
do this, we can treat Objective-C base classes as "sealed and safe"
for the purpose of initialization, and help programmers reason
effectively about initialization and their class invariants.

Here are the proposed rules:

* ``init`` methods of base classes defined in Objective-C are not, by
  default, part of the public interface of a subclass defined in
  Swift.

* ``init`` methods of base classes defined in Swift are not, by
  default, part of the public interface of a subclass defined in
  Objective-C.

* ``self.init(...)`` calls in Swift never dispatch virtually.  We have a
  safe model for "virtual initialization:" ``init`` methods can call
  overridable methods after all instance variables and superclasses
  are initialized.  Allowing *virtual* constructor delegation would
  undermine that safety.

* As a convenience, when a subclass' instance variables all have
  initializers, it should be possible to explicitly expose superclass
  init methods in a Swift subclass without writing out complete
  forwarding functions.  For example::

    @inherit init(x:y:z) // one possible syntax

  .. Note:: Allowing ``@inherit init(*)`` is a terrible idea

     It allows superclasses to break their subclasses by adding
     ``init`` methods.


Summary
=======

By eliminating by-default ``init``\ method inheritance and disabling
virtual dispatch in constructor delegation, we give class designers
full control over the state of their constructed instances.  By
preserving virtual dispatch for non-``self``, non-``super`` calls to
``init`` methods, we allow Objective-C programmers to keep using the
patterns that depend on virtual dispatch, including designated
initializers and ``initWithCoder`` methods.