1
+ // ------------------------------------------------------------------------------------------------
2
+ // Things to know:
3
+ //
4
+ // * Properties store values in classes, structures and enumerations.
5
+ // ------------------------------------------------------------------------------------------------
6
+
7
+ // Here's a structure with a couple simple stored properties:
8
+ struct FixedLengthRange
9
+ {
10
+ var firstValue : Int
11
+ let length : Int
12
+ }
13
+
14
+ // Structures must have all of their properties initialized upon instantiation.
15
+ //
16
+ // This won't compile since the struct includes properties that havn't been initialized with
17
+ // default values:
18
+ //
19
+ // var anotherRangeOfThreeItems = FixedLengthRange()
20
+ //
21
+ // In order to instantiate a FixedLengthRange struct, we must use the memberwise initializer. Note
22
+ // that this will initialize a constant property and a variable property inside the struct:
23
+ var rangeOfThreeItems = FixedLengthRange ( firstValue: 0 , length: 3 )
24
+ rangeOfThreeItems. firstValue = 6
25
+
26
+ // ------------------------------------------------------------------------------------------------
27
+ // Lazy Stored Properties
28
+ //
29
+ // A Lazy Stored Property is a value that is not calculated until its first use.
30
+ //
31
+ // They are declared using the "@lazy" attribute and may not be constant.
32
+ //
33
+ // Global and local variables are all lazy, except that they don't need the @lazy attribute.
34
+ //
35
+ // Here, we'll define a couple classes to showcase Lazy Stored Properties. In this example, let's
36
+ // assume that DataImporter is a time-consuming process, and as such, we will want to use a lazy
37
+ // stored property whenever we use it. This way, if we end up never using it, we never pay the
38
+ // penalty of instantiating it.
39
+ class DataImporter
40
+ {
41
+ var filename = " data.txt "
42
+ }
43
+
44
+ class DataManager
45
+ {
46
+ @lazy var importer = DataImporter ( )
47
+ var data = String [ ] ( )
48
+ }
49
+
50
+ // Now let's instantiate the data manager and add some simple data to the class:
51
+ let manager = DataManager ( )
52
+ manager. data += " Some data "
53
+ manager. data += " Some more data "
54
+
55
+ // Notice how we haven't used the importer yet, so it is nil:
56
+ manager
57
+
58
+ // So now let's access it:
59
+ manager. importer. filename
60
+
61
+ // And now we see the importer was created:
62
+ manager
63
+
64
+ // ------------------------------------------------------------------------------------------------
65
+ // Computed Properties
66
+ //
67
+ // Computed properties don't store data, but rather use getters and setters for accessing values
68
+ // that are computed up on request.
69
+ //
70
+ // Computed Properties are available for global as well as local variables.
71
+ //
72
+ // We'll start with a few structures that we'll use to show how computed properties work.
73
+ struct Point
74
+ {
75
+ var x = 0.0 , y = 0.0
76
+ }
77
+ struct Size
78
+ {
79
+ var width = 0.0 , height = 0.0
80
+ }
81
+
82
+ // The following structure includes a computed property with a Point type named 'center'. Notice
83
+ // that 'center' is variable (not constant) which is a requirement of computed properties.
84
+ //
85
+ // Every computed property must have a getter, but does not need a setter. If we provide a setter,
86
+ // we need to know the new value being assigned to the computed property. We've called this
87
+ // value 'newCenter'. Note that this value does not have a type annotation because the computed
88
+ // property already knows that it is a Point type. Providing a type annotation would be an error.
89
+ struct Rect
90
+ {
91
+ var origin = Point ( )
92
+ var size = Size ( )
93
+ var center : Point
94
+ {
95
+ get
96
+ {
97
+ let centerX = origin. x + ( size. width / 2 )
98
+ let centerY = origin. y + ( size. height / 2 )
99
+ return Point ( x: centerX, y: centerY)
100
+ }
101
+ set ( newCenter)
102
+ {
103
+ origin. x = newCenter. x - ( size. width / 2 )
104
+ origin. y = newCenter. y - ( size. height / 2 )
105
+ }
106
+ }
107
+ }
108
+
109
+ // Here, we'll create a square from our Rect structure
110
+ var square = Rect ( origin: Point ( x: 0.0 , y: 0.0 ) , size: Size ( width: 10.0 , height: 10.0 ) )
111
+
112
+ // We can now get the center point, computed from the Rect's origin and size. Being a computed
113
+ // property, we can treat it just like any other peroperty.
114
+ let initialSquareCenter = square. center
115
+
116
+ // Since we provided a setter, we can also set the center point as if it is a stored property.
117
+ // This will effectively update the Rect's origin and size based on the specified center point.
118
+ square. center = Point ( x: 15 , y: 15 )
119
+
120
+ // We can see that the origin has been updated from (0, 0) to (10, 10):
121
+ square. origin
122
+
123
+ // Shorthand Setter Declaration
124
+ //
125
+ // The computed property's setter from the Rect structure provided a parameter on the setter named
126
+ // 'newCenter'. If we don't specify this parameter, Swift will automatically generate an input
127
+ // value named 'newValue' for us.
128
+ //
129
+ // Here, AlternativeRect is the same declaration as Rect above, except that the setter uses
130
+ // Swift's default setter value, 'newValue':
131
+ struct AlternativeRect
132
+ {
133
+ var origin = Point ( )
134
+ var size = Size ( )
135
+ var center : Point
136
+ {
137
+ get
138
+ {
139
+ let centerX = origin. x + ( size. width / 2 )
140
+ let centerY = origin. y + ( size. height / 2 )
141
+ return Point ( x: centerX, y: centerY)
142
+ }
143
+ set
144
+ {
145
+ origin. x = newValue. x - ( size. width / 2 )
146
+ origin. y = newValue. y - ( size. height / 2 )
147
+ }
148
+ }
149
+ }
150
+
151
+ // We can also have a read-only computed property by simply omitting the setter:
152
+ struct Cube
153
+ {
154
+ var width = 0.0 , height = 0.0 , depth = 0.0
155
+ var volume : Double
156
+ {
157
+ get
158
+ {
159
+ return width * height * depth
160
+ }
161
+ }
162
+ }
163
+
164
+ // Alternatively, Swift allows us to shorten the syntax of a read-only computed property by
165
+ // omitting the get{} construct and inserting the code for the getter directly into the property
166
+ // declaration:
167
+ struct AnotherCube
168
+ {
169
+ var width = 0.0 , height = 0.0 , depth = 0.0
170
+ var volume : Double
171
+ {
172
+ return width * height * depth
173
+ }
174
+ }
175
+
176
+ // Let's declare our structure and read the 'volume' property
177
+ var cube = AnotherCube ( width: 4 , height: 5 , depth: 2 )
178
+ cube. volume
179
+
180
+ // Since the 'volume' property is read-only, if we tried to assign a value to it, it would
181
+ // would generate a compiler error.
182
+ //
183
+ // The following line of code will not compile:
184
+ //
185
+ // cube.volume = 8.0
186
+
187
+ // ------------------------------------------------------------------------------------------------
188
+ // Property Observers
189
+ //
190
+ // Property observers allow us to respond to changes in a property's value. We can declare an
191
+ // observer that contains our code that is run just before a property is set (optionally allowing
192
+ // us to alter the value being set) and an observer that is run just after a property has been
193
+ // modified.
194
+ //
195
+ // Property observers are available for global as well as local variables.
196
+ //
197
+ // These observers are not called when a property is first initialized, but only when it changes.
198
+ //
199
+ // Similar to setters, each 'willSet' and 'didSet' will receive a parameter that represents (in
200
+ // the case of 'willSet') the value about to be assigned to the property and (in the case of
201
+ // 'didSet') the value that was previously stored in the property prior to modification.
202
+ class StepCounter
203
+ {
204
+ var totalSteps : Int = 0
205
+ {
206
+ willSet( newTotalSteps)
207
+ {
208
+ " About to step to \( newTotalSteps) "
209
+ }
210
+ didSet( oldTotalSteps)
211
+ {
212
+ " Just stepped from \( oldTotalSteps) "
213
+ }
214
+ }
215
+ }
216
+
217
+ // Let's create an instance of StepCounter so we can try out our observer
218
+ let stepCounter = StepCounter ( )
219
+
220
+ // The following will first call 'willSet' on the 'totalSteps' property, followed by a change to
221
+ // the value itself, followed by a call to 'didSet'
222
+ stepCounter. totalSteps = 200 ;
223
+
224
+ // Similar to setters, we can shorten our observers by omitting the parameter list for each. When
225
+ // we co this, Swift automatically provides parameters named 'newValue' and 'oldValue'
226
+ class StepCounterShorter
227
+ {
228
+ var totalSteps : Int = 0
229
+ {
230
+ willSet{ " About to step to \( newValue) " }
231
+ didSet { " Just stepped from \( oldValue) " }
232
+ }
233
+ }
234
+
235
+ // We can also override the value being set by modifying the property itself within the 'didSet'
236
+ // observer. This only works in the 'didSet'. If you attempt to modify the property in 'willSet'
237
+ // you will receive a compiler warning.
238
+ //
239
+ // Let's try wrapping our value to the range of 0...255
240
+ class StepCounterShorterWithModify
241
+ {
242
+ var totalSteps : Int = 0
243
+ {
244
+ willSet{ " About to step to \( newValue) " }
245
+ didSet { totalSteps = totalSteps & 0xff }
246
+ }
247
+ }
248
+ var stepper = StepCounterShorterWithModify ( )
249
+ stepper. totalSteps = 345
250
+ stepper. totalSteps // This reports totalSteps is now set to 89
251
+
252
+ // ------------------------------------------------------------------------------------------------
253
+ // Type Properties
254
+ //
255
+ // Until now, we've been working with Instance Properties and Instance Methods, which are
256
+ // associated to an instance of a class, structure or enumeration. Each instance gets its own copy
257
+ // of the property or method.
258
+ //
259
+ // Type properties are properties that are attached to the class, structure or enumeration's type
260
+ // itself and not any specific instance. All instances of a type will share the same Type Property.
261
+ //
262
+ // These are similar to 'static' properties in C-like languages.
263
+ //
264
+ // For Value types (structs, enums) we use the 'static' keyword. For Class types with the 'class'
265
+ // keyword.
266
+ //
267
+ // Type properties can be in the form of stored properties or computed properties. As with normal
268
+ // stored or computed properties, all the same rules apply except that stored type properties must
269
+ // always have a default value. This exception is necessary because the initializer (which we'll
270
+ // learn about later) is associated to a specific instance and as such, cannot be reliable for
271
+ // initializing type properties.
272
+ //
273
+ // Here is a class with a couple of type properties
274
+ struct SomeStructure
275
+ {
276
+ static var storedTypeProperty = " some value "
277
+
278
+ // Here's a read-only computed type property using the short-hand read-only syntax
279
+ static var computedTypeProperty : Int { return 4 }
280
+ }
281
+
282
+ // Similarly, here's an enumeration with a couple of type properties
283
+ enum SomeEnum
284
+ {
285
+ static let storedTypeProperty = " some value "
286
+
287
+ static var computedTypeProperty : Int { return 4 }
288
+ }
289
+
290
+ // Classes are a little different in that they cannot contain stored type properties, but may
291
+ // only contain computed type properties
292
+ class SomeClass
293
+ {
294
+ // The following line won't compile because classes aren't allowed stored type properties
295
+ //
296
+ // class var storedTypeProperty = "some value"
297
+
298
+ // This is read-only, but you can also do read/write
299
+ class var computedTypeProperty : Int { return 4 }
300
+ }
0 commit comments