Properties(属性)
//属性
//“属性将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。”
//“存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。”
//“还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。”
//1.1存储属性
//“一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)”
//“可以在定义存储属性的时候指定默认值,请参考默认构造器一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值”“请参考构造过程中常量属性的修改一节。”
struct FixedLengthRange{
var firstValue:Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue:0,length:3)
rangeOfThreeItems.firstValue = 6
//1.2 常量结构体的存储属性
//“创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行”
let rangeOfThreeItemsState = FixedLengthRange(firstValue:0,length:3)
//rangeOfThreeItemsState.firstValue = 6 报错
//“因为 rangeOfFourItems 被声明成了常量(用 let 关键字),即使 firstValue 是一个变量属性,也无法再修改它了。
//这种行为是由于结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
//属于引用类型的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性”
//1.3 延迟存储属性
//“延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy 来标示一个延迟存储属性”
//“必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性”
//“延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算它。”
class DataImporter{
/*
假设这个类初始化会消耗不少时间
*/
var fileName = "data.txt"
}
class DataManager{
lazy var importer = DataImporter()
var data = [String]()
//提供数据管理的类
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
//“由于使用了 lazy ,importer 属性只有在第一次被访问的时候才被创建。比如访问它的属性 fileName”
print(manager.importer.fileName)
//输出“data.txt”
//注意:“如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次”
//2.1 计算属性
//“类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 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
print(initialSquareCenter)
square.center = Point(x:15.0,y:15.0)
print("square.origin is now \(square.origin)")
//“这个例子定义了 3 个结构体来描述几何形状:
/*
Point 封装了一个 (x, y) 的坐标
Size 封装了一个 width 和一个 height”
Rect 表示一个有原点和尺寸的矩形
*/
//“Rect也提供了一个名为center 的计算属性。一个矩形的中心点可以从原点(origin)和大小(size)算出,所以不需要将它以显式声明的 Point 来保存。Rect 的计算属性 center 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。”
//2.2 简化setter声明
//“如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。下面是使用了简化 setter 声明的 Rect 结构体代码”
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)
}
}
}
//2.3只读计算属性
//“只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值”
//“必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值”
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 is \(fourByFiveByTwo.volume)")
//打印40
//“为 volume 提供 setter 毫无意义,因为无法确定如何修改 width、height 和 depth 三者的值来匹配新的 volume。然而,Cuboid 提供一个只读计算属性来让外部用户直接获取体积是很有用的”
//3.1 属性观察器
//“属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。”
//“可以为 除了延迟存储属性之外 的 其他存储属性 添加属性观察器,也可以通过重写属性的方式为 继承的属性(包括存储属性和计算属性) 添加属性观察器。你 不必为 非重写的计算属性 添加属性观察器,因为可以 通过它的 setter 直接监控和响应值的变化”
// 可以为属性添加如下的一个或者全部观察器:
//willSet 在新的值被设置之前调用
//didSet 在新的值被设置之后立即调用
//“willSet 观察器会将新的属性值作为常量参数传入,在 willSet 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue 表示。
//“didSet 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 oldValue。如果在 didSet 方法中再次对该属性赋值,那么新值会覆盖旧的值。”
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 stepConter = StepCounter()
stepConter.totalSteps = 200
//about to set totalSteps to 200
//added 200 steps
stepConter.totalSteps = 360
//about to set totalSteps to 360
//added 160 steps
//4.全局变量和局部变量
//“计算属性和属性观察器所描述的功能也可以用于全局变量和局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。”
//“注意全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记lazy修饰符。
//局部范围的常量或变量从不延迟计算。
//5.1类型属性
//“实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立”
//“也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性”
//“存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。”
//“注意 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。”
// 注意 “存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 lazy 修饰符。
//5.2类型属性语法
//“在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(global)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内”
//“使用关键字 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 overrideableComputedProperty: Int {
return 107
}
}
//“注意 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同”
//5.3 “获取和设置类型属性的值”
//“跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例”
print(SomeStructure.storedTypeProperty)
//打印some value
SomeStructure.storedTypeProperty = "another value"
print(SomeStructure.storedTypeProperty)
//打印another value
print(SomeClass.computedTypeProperty)
//打印27
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel:Int = 0{
didSet{
if currentLevel > AudioChannel.thresholdLevel {
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
//输出7
print(AudioChannel.maxInputLevelForAllChannels)
//输出7
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
//输出10
print(AudioChannel.maxInputLevelForAllChannels)
//输出10