Stored properties
Only provided by classed and structures.
Computed properties
Provide by classes, structures, enumerations.
Usually associated with instance of a particular type, can be associated with the type itself(type properties).
Can define property observers to monitor changes in a property’s value, which can respond to with custom action.
Can use a property wrapper to reuse code in the getter and setter.
Stored Properties
**struct Setting {
var width: Int
let height: Int
}
var appSetting = Setting(width: 100, height: 200)
appSetting.width = 150
let constSetting = Setting(width: 110, height: 210)
//constSetting.width = 120 // This will trigger a compile-time error, the struct is of const value type.**
Lazy Stored Properties
Its initial value isn’t calculated until the first time it’s used.
Lazy stored properties must be a variable, it will be assigned at the first time it’s used.
Constants can’t be lazy, because it always have a value before initialization completes.
class DataImporter {
var fileName = "data.txt"
func readFromFile(path: String) -> String {
return "some " + path
}
}
class DataManager {
lazy var importer = DataImporter()
var data: [String] = []
}
let manager = DataManager()
manager.data.append("Happy")
manager.data.append("Time")
// Here the manager.importer has not yet been created.
var str = manager.importer.readFromFile(path: manager.data[0]) // Here the manager.importer is created, and then call the function of the manager.importer
print(str)
// Print: 「some Happy」
- If a property marked with the
lazy
modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.
Stored Properties and Instance Variable
In Objective-C or C++, the definition and initialization of an instance variable are separated in different places.
Swift cancels the instance variable rules, and use a simple way to declare the propertied of an instance, define the name, type, memory management characteristics in a single location.
Computed Properties
In classes, structures, and enumerations, we provide a getter and setter to retrieve and set properties and values indirectly.
Using computed properties, we can compute something else of the class’s from the other properties.
struct Bag {
var bookCount: Int = 0
var penCount: Int = 0
var objectCount: Int {
get {
return bookCount + penCount
}
set (count) {
bookCount = Int(Double(count) * 0.8)
penCount = Int(Double(count) * 0.2)
}
}
}
var bag = Bag(bookCount: 1, penCount: 2)
print(bag.objectCount) // Here use the getter. Print 「3」
bag.objectCount = 10 // Here use the setter. The temporary parameter 'count' is assignes to 10.
print(bag.objectCount) // 10
print(bag.bookCount) // 8
print(bag.penCount) // 2
The setter has a shorter form of declaration. It must use the newValue
to grab the temporary parameter.
set {
bookCount = Int(Double(newValue) * 0.8)
penCount = Int(Double(newValue) * 0.2)
}
The getter has a shorter form of declaration. It write the code with one line and as the return expression.
get {
bookCount + penCount
}
Read-Only Computed Properties
struct Bag {
var bookCount: Int = 0
var penCount: Int = 0
var objectCount: Int {
get {
bookCount + penCount
}
}
}
Read-Only in a shorter form:
struct Bag {
var bookCount: Int = 0
var penCount: Int = 0
var objectCount: Int {
return bookCount + penCount
}
}
Omit the return
struct Bag {
var bookCount: Int = 0
var penCount: Int = 0
var objectCount: Int {
bookCount + penCount
}
}
The read-only computed properties are useful for an external user to discover the Bag
’s current status.
Property Observers
The observer can observe and respond to changes in a property’s value.
The observer can’t be provide together with getter or setter.
Only can add property in these places:
Stored properties that we define.
Stored properties that we inherit.
For an inherit property, add a property observer by overriding that property in a subclass.
Computed properties that we inherit.
Use property’s setter to observe and responds to value changes, instead of creating an observer.
Define either or both of these observers on a property.
willSet
is called before the value is stored. (pass the new property value as a constant, it has a default name ofnewValue
)disSet
is called immediately after the new value is stored.(pass the last old property value as a constant, it has a default name ofoldValue
)
struct Bag {
var bookCount: Int = 0
var penCount: Int = 0
var objectCount: Int {
willSet (newCount) {
print("Will set the count \(newCount)") // 2
}
didSet {
print("Has set the count, the olValue is \(oldValue), and add \(objectCount - oldValue)") // 0 2
}
}
}
var bag = Bag(objectCount: 0)
bag.objectCount = 2 // Here trigger the wiilSet and then trigger the didSet. The newValue is 2, and the oldValue is 0.
print("After change, objectCount: \(bag.objectCount)") // 2
The observer in the superclass can observe the properties’ action in the subclass.
- The subclass set the superclass’s property after the superclass’s initializer has been called.
- If the superclass’s initializer hasn’t been called, the observer can not work.
Property Wrappers
It can wrap the data and the code together as a code block. Using property wrapper can reduce the repeated codes.
Here is an example that store a number less than 12 or equals to 12. The wrappedValue must used explicitly.
@propertyWrapper
struct Num {
private var number = 0
var wrappedValue: Int {
get {return number}
set {number = min(newValue, 12)}
}
}
Using
struct Rectangle {
@Num var width: Int // It declares that the var use the rule of the propertyWrapper
@Num var height: Int
}
var rect = Rectangle()
print(rect.width) // Call the 'get' function
rect.height = 10 // Call the 'set' function
Another way to use the propertyWrapper
struct Rectangle {
private var _height = Num()
private var _width = Num()
var height: Int {
get{ return _height.wrappedValue }
set{ _height.wrappedValue = newValue}
}
var width: Int {
get{ return _width.wrappedValue}
set{ _width.wrappedValue = newValue}
}
}
Setting Initial Values for Wrapped Properties
In default, use the wrppedValue
to catch the input implicitly.
Override the initial function so to provide several ways to to init a Num
.
@propertyWrapper
struct Num {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number}
set { number = min(newValue, maximum)}
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = wrappedValue
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = wrappedValue
}
}
The initialization without parameter.
struct ZeroRectangle {
@Num var width: Int
@Num var height: Int
}
var zeroRect = ZeroRectangle()
The initialization with one default parameter wrappedValue
.
struct UnitRectangle {
@Num var width: Int = 1 // It call the init
@Num var height: Int = 1
}
var unitRect = UnitRectangle()
// Or in this way
struct UnitRectangle {
@Num(wrappedValue: 1) var width: Int
@Num(wrappedValue: 1) var height: Int
}
var unitRect = UnitRectangle()
The initialization with two parameters.
struct NarrowRectangle {
@Num(wrappedValue: 2, maximum: 5) var width: Int
@Num(wrappedValue: 3, maximum: 4) var height: Int
}
var narrowRect = NarrowRectangle()
// Or in this way
struct NarrowRectangle {
@Num(wrappedValue: 2, maximum: 5) var width: Int
@Num(maximum: 4) var height: Int = 3
}
var narrowRect = NarrowRectangle()
The default parameter wrappedValue
is used to pass input implicitly.
Project a Value From a Property Wrapper
Declare the var name projectedValue
in a property wrapper.
It can store some information such as track the change of the property, and then the outside caller can get the information from the projectedValue.
It can be accessed via the dollar sign $
, when the outside caller has the permission to access.
struct Num {
private var number: Int
private(set) var projectedValue: Bool // The projectedValue's set function is declare as private.
var wrappedValue: Int {
get { return number}
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.projectedValue = false
self.number = 0
}
}
struct Rectangle {
@Num var width: Int
}
var rect = Rectangle()
rect.width = 15 // The number is private, it will be affect via the `set` function.
print(rect.$width) // Use the dollar sign to access the projectedValue.
// rect.$width = false // It can't work, the outside caller can't call the projectedValue's private function 'set'
Global and Local Variables
Global
It is defined outside any function, method, closure, or type context.
Global constants and variables are always lazily.
Local
It is defined in any function, method, closure, or type context.
Local constants and variables are never computed lazily.
Stored Variables
- provide storage for a value of a certain type and allow that value to be set and retrieved.
Observer is available for the stored variable and computed variable in a global or local scope.
Property wrapper can be use to a stored variable, but not to a global variable or a computed variable.
Here’s a function’s local stored variable (count) use the wrapper property (Num).
func doSomething() {
@Num var count: Int
_count.number = 12 // The 'number' requires the wrapper 'Num' so it use a underscore to indicate the wrapper.
print(_count.number)
}
Instance Properties
Every time we create a new instance of that type, it has it own set of property values, separate from any other instance.
Type Properties
- If we define a property belong to the type itself, it’s called type property. All instance of that type share this one type property. (like static property in Java class)
- Stored type properties can be variables or constants.
- Computed type properties are always declared as variable properties, in the same way as computed instance properties.
- Need default value.
- Lazily initialized on first access. Be initialized only once, even when accessed by multiple threads simultaneously.
Type Property Syntax
Define type property with static
keyword.
In a class, use class
instead the static
to override the superclass’s implementation.
struct ShortSize {
static var name = "Short"
private static var store: Int = 1
static var size: Int {
get { return store}
set { store = newValue}
}
}
enum MediumSize {
static var name = "Medium"
private static var store: Int = 4
static var size: Int {
get { return store }
set { store = newValue}
}
}
class LongSize {
static var name = "Long"
private static var store: Int = 4
static var size: Int {
get { return store}
set { store = newValue}
}
class var sizeFromSuper: Int {
get { return store }
set { store = 2 * newValue}
}
}
// Use the type properties
print(ShortSize.name)
print(ShortSize.size)
Setting the Type Properties
setting
struct ShortSize { static var name = "Short" private static var store: Int = 1 var size: Int { get { return ShortSize.store} set { ShortSize.store = newValue} } } var shortSize = ShortSize() var shortSizeAnother = ShortSize() print(shortSize.size) print(shortSizeAnother.size) shortSize.size = 15 // It will change the sharing variable 'store' (type property) print(shortSize.size) print(shortSizeAnother.size)
Result
1 1 15 15
Use the observer to respond to the change of the variable and to affect the type property.
struct Channel {
static let maxLevel: Int = 10
static var maxInput: Int = 0
var currentLevel: Int {
didSet {
if currentLevel > Channel.maxLevel {
currentLevel = Channel.maxLevel // It set the currentLevel, but it doesn't call the observer again.
} else if currentLevel > Channel.maxInput {
Channel.maxInput = currentLevel
}
}
}
}
var channel = Channel(currentLevel: 0)
channel.currentLevel += 4
print(Channel.maxInput)