mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
286 lines
7.6 KiB
ReStructuredText
286 lines
7.6 KiB
ReStructuredText
=============================
|
|
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`_
|
|
|