- 属性将值与特定类,结构或枚举相关联;
- 存储和计算属性通常与特定类型的实例相关联。但是属性也可以和类型本身相关联(类属性);
- 存储属性将常量和变量的值存储在实例中,而计算属性则计算实例中的属性值;
- 计算属性由类,枚举,结构体提供。存储属性仅有类和结构体提供;
- 属性观察器以监视属性值的更改;
一、 存储属性
- 存储属性是一个常量或变量,它存储为特定类或结构实例的一部分;
- 存储的属性可以是变量存储属性(var),也可以是常量存储属性(let);
- 可以为存储属性提供默认值作为其定义的一部分;
- 可以在初始化期间设置和修改存储属性的初始值,即使是常量存储属性也可以;
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
(一)常量结构体实例的存储属性
- 结构体是值类型。当值类型的实例标记为常量时,其所有属性也都标记为常量;
- 将创建结构的实例分配给常量,则无法修改该实例的属性,即使这些属性被声明为变量属性:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
(二)懒加载存储属性
- 懒加载存储属性是在属性声明前面添加
lazy
修饰符来表示; - 懒加载存储属性只有在首次使用时才计算其初始值;
- 当属性的初始值依赖于外部因素时,使用懒加载属性,这些外部因素的值在实例初始化完成后才知道;
- 当属性的初始值需要复杂或计算代价昂贵的设置时,使用懒加载属性,除非需要,否则不应该执行这些设置;
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial amount of time to initialize.
*/
var filename = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
注意:
懒加载属性必须声明为变量,因为只有实例初始化完成后,才能检索到它的初始值;而常量属性必须在初始化完成之前赋值,因此不能被声明为懒加载。
如果多个线程同时访问一个未初始化的懒加载属性,则无法保证该属性只被初始化一次。
(三)存储属性和实例变量
- 在Objective-C语言中,除了属性之外,还可以使用实例变量作为存储在属性中的值的备份存储。
- Swift属性没有对应的实例变量,而且不会直接访问属性的备份存储。
二、 计算属性
- 在类,结构体和枚举中都可以定义计算属性,计算属性并不存储值;
- 计算属性提供了一个getter和一个可选的setter方法来间接的检索和设置其他属性的值;
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"
(一)简写的Setter方法声明
- 如果计算属性的setter方法没有为新值定义名称,则使用默认名称newValue。
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
(二)只读的计算属性
- 只读的计算属性中只包含getter方法,没有setter方法;
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
三、属性观察者
- 在属性中定义willSet或didSet观察者,来观察和响应属性值的变化:
(1).willSet在存储值之前被调用。
(2). didSet在存储新值之后立即被调用。 - willSet默认的参数名是newValue,didSet默认的参数名是oldValue;
- 当每次给属性设置新值时,都会调用属性观察者,即使属性的新值与当前值相同;
- 可以为任何的存储属性添加属性观察者,而懒加载存储属性除外;
- 可以在子类中通过重写属性,给继承的属性(存储属性或计算属性)添加属性观察者;
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
四、全局和局部变量
上面描述的用于计算和观察属性的功能也可用于全局变量和局部变量。 全局变量是在任何函数、方法、闭包或类型上下文之外定义的变量。 局部变量是在函数、方法或闭包上下文中定义的变量。
在前面章节中遇到的全局变量和局部变量都是存储变量。 存储变量(如存储的属性)为特定类型的值提供存储,并允许设置和检索该值。
但是,还可以在全局或本地范围内定义计算变量并为存储变量定义观察者。 计算变量计算它们的值,而不是存储它们,它们的编写方式与计算属性相同。
五、类属性
- 类属性属于类本身,无论创建多少个实例,这些属性只有一个副本;
- 存储类属性可以声明为变量或常量,计算类属性只能被声明为变量;
- 存储类属性必须设置默认值,因为类型本身没有初始化器,不能在初始化时,给存储类属性赋值;
- 存储类型属性只在首次访问时被惰性初始化。它们能够保证只被初始化一次,即使由多个线程同时访问,而且不需要使用lazy修饰符标记;
(一)类属性语法
- 使用
static
关键字定义类型属性; - 对于类的计算类型属性可以使用
class
关键字声明,这样可以允许子类重写父类的实现;
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
(二)查询和设置类型属性
- 对于类型属性的操作,只能使用类型来查询和设置其值;
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"