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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
|
=============================
Stored and Computed Variables
=============================
.. warning:: This document has not been updated since the initial design in
Swift 1.0.
Variables are declared using the ``var`` keyword. These declarations are valid
at the top level, within types, and within code bodies, and are respectively
known as *global variables,* *member variables,* and *local variables.*
Member variables are commonly referred to as *properties.*
Every variable declaration can be classified as either *stored* or *computed.*
Member variables inherited from a superclass obey slightly different rules.
.. contents:: :local:
Stored Variables
================
The simplest form of a variable declaration provides only a type::
var count : Int
This form of ``var`` declares a *stored variable.* Stored variables cause
storage to be allocated in their containing context:
- a new global symbol for a global variable
- a slot in an object for a member variable
- space on the stack for a local variable
(Note that this storage may still be optimized away if determined unnecessary.)
Stored variables must be initialized before use. As such, an initial value can
be provided at the declaration site. This is mandatory for global variables,
since it cannot be proven who accesses the variable first. ::
var count : Int = 10
If the type of the variable can be inferred from the initial value expression,
it may be omitted in the declaration::
var count = 10
Variables formed during pattern matching are also considered stored
variables. ::
switch optVal {
case .Some(var actualVal):
// do something
case .None:
// do something else
}
Computed Variables
==================
A *computed variable* behaves syntactically like a variable, but does not
actually require storage. Instead, accesses to the variable go through
"accessors" known as the *getter* and the *setter.* Thus, a computed variable
is declared as a variable with a custom getter::
struct Rect {
// Stored member variables
var x, y, width, height : Int
// A computed member variable
var maxX : Int {
get {
return x + width
}
set(newMax) {
x = newMax - width
}
}
// myRect.maxX = 40
In this example, no storage is provided for ``maxX``.
If the setter's argument is omitted, it is assumed to be named ``value``::
var maxY : Int {
get {
return y + height
}
set {
y = value - height
}
}
Finally, if a computed variable has a getter but no setter, it becomes a
*read-only variable.* In this case the ``get`` label may be omitted.
Attempting to set a read-only variable is a compile-time error::
var area : Int {
return self.width * self.height
}
}
Note that because this is a member variable, the implicit parameter ``self`` is
available for use within the accessors.
It is illegal for a variable to have a setter but no getter.
Observing Accessors
===================
Occasionally it is useful to provide custom behavior when changing a variable's
value that goes beyond simply modifying the underlying storage. One way to do
this is to pair a stored variable with a computed variable::
var _backgroundColor : Color
var backgroundColor : Color {
get {
return _backgroundColor
}
set {
_backgroundColor = value
refresh()
}
}
However, this contains a fair amount of boilerplate. For cases where a stored
property provides the correct storage semantics, you can add custom behavior
before or after the underlying assignment using "observing accessors"
``willSet`` and ``didSet``::
var backgroundColor : Color {
didSet {
refresh()
}
}
var currentURL : URL {
willSet(newValue) {
if newValue != currentURL {
cancelCurrentRequest()
}
}
didSet {
sendNewRequest(currentURL)
}
}
A stored property may have either observing accessor, or both. Like ``set``,
the argument for ``willSet`` may be omitted, in which case it is provided as
"value"::
var accountName : String {
willSet {
assert(value != "root")
}
}
Observing accessors provide the same behavior as the two-variable example, with
two important exceptions:
- A variable with observing accessors is still a stored variable, which means
it must still be initialized before use. Initialization does not run the
code in the observing accessors.
- All assignments to the variable will trigger the observing accessors with
the following exceptions: assignments in the init and destructor function for
the enclosing type, and those from within the accessors themselves. In this
context, assignments directly store to the underlying storage.
Computed properties may not have observing accessors. That is, a property may
have a custom getter or observing accessors, but not both.
Overriding Read-Only Variables
==============================
If a member variable within a class is a read-only computed variable, it may
be overridden by subclasses. In this case, the subclass may choose to replace
that computed variable with a stored variable by declaring the stored variable
in the usual way::
class Base {
var color : Color {
return .Black
}
}
class Colorful : Base {
var color : Color
}
var object = Colorful(.Red)
object.color = .Blue
The new stored variable may have observing accessors::
class MemoryColorful : Base {
var oldColors : Array<Color> = []
var color : Color {
willSet {
oldColors.append(color)
}
}
}
A computed variable may also be overridden with another computed variable::
class MaybeColorful : Base {
var color : Color {
get {
if randomBooleanValue() {
return .Green
} else {
return super.color
}
}
set {
print("Sorry, we choose our own colors here.")
}
}
}
Overriding Read-Write Variables
===============================
If a member variable within a class as a read-write variable, it is not
generally possible to know if it is a computed variable or stored variable.
A subclass may override the superclass's variable with a new computed variable::
class ColorBase {
var color : Color {
didSet {
print("I've been painted \(color)!")
}
}
}
class BrightlyColored : ColorBase {
var color : Color {
get {
return super.color
}
set(newColor) {
// Prefer whichever color is brighter.
if newColor.luminance > super.color.luminance {
super.color = newColor
} else {
// Keep the old color.
}
}
}
}
In this case, because the superclass's ``didSet`` is part of the generated
setter, it is only called when the subclass actually invokes setter through
its superclass. On the ``else`` branch, the superclass's ``didSet`` is skipped.
A subclass may also use observing accessors to add behavior to an inherited
member variable::
class TrackingColored : ColorBase {
var prevColor : Color?
var color : Color {
willSet {
prevColor = color
}
}
}
In this case, the ``willSet`` accessor in the subclass is called first, then
the setter for ``color`` in the superclass. Critically, this is *not* declaring
a new stored variable, and the subclass will *not* need to initialize ``color``
as a separate member variable.
Because observing accessors add behavior to an inherited member variable, a
superclass's variable may not be overridden with a new stored variable, even
if no observing accessors are specified. In the rare case where this is
desired, the two-variable pattern shown above__ can be used.
__ `Observing Accessors`_
|