blob: 22dd6255e896c598b2840748a965c32fd0b14923 [file] [log] [blame]
=============================
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`_