swift 学习- 11 -- 属性

// '属性'将值跟特定的类, 结构体或枚举关联, 存储属性常量或变量作为实例的一部分,而计算属性计算(不是存储) 一个值, 计算属性可以用于 类, 结构体, 枚举, 存储属性只能用于 类 和 结构体

// 存储属性和 计算属性 通常与特定类型的实例关联, 但是, 属性也可以直接作用类型本身, 这种属性称为类型属性

// 还可以定义 属性观察器 来监控属性值的变化, 以此来触发一个自定义的操作, 属性观察器可以添加到自己定义的存储属性上, 也可以添加到从父类集成的属性上

// 存储属性

// 简单来说, 一个存储属性就是存储在特定类或结构体实例里的一个常量或变量, 存储属性可以是变量存储属性, 也可以是常量存储属性

// 可以在定义存储属性的时候指定默认值

struct FixedLengthRange {

var firstValue: Int

let length: Int

}

var ranheOfThreeItems = FixedLengthRange(firstValue:0, length:3)

ranheOfThreeItems.firstValue = 6

// 在例子中, length 在创建实例的时候被初始化, 因为它是一个常量存储属性,所以之后无法修改它的值

// 常量结构体的存储属性

// 如果创建了一个结构体的实例并将其赋值给一个常量, 则无法修改该实例的任何属性, 即使有属性被声明为变量也比不行

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 6)

//rangeOfFourItems.firstValue = 4 // 此处报错

// 因为 rangeOfFourItems 被声明为了常量, 即使 firstValue 是一个变量属性, 也无法在修改它;

// 这种行为是由于结构体属于值类型, 当值类型的实例被声明为常量的时候, 他的所有属性也就成了常量

// 属于引用类型的 类 (class) 则不一样, 吧一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性

// 延迟存储属性

// 延时存储属性 是指当第一次被调用的时候才会计算其初始值的属性, 在属性声明前使用 lazy 来表示一个延迟存储属性

// 注意 : 必须将延迟存储属性声明成变量 (var), 因为属性的初始值可能在实例构造完成之后才会得到, 而常量属性在构造过程完成之前必须有初始值,因此无法声明成延迟属性

// 延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时, 可能只在需要的时候计算他

// 下面的例子使用了延迟属性来避免复杂类中不必要的初始化, 例子中定义了 DataImporter 和 DataManager 两个类, 下面是部分代码

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")

// DataImporter 实例的 importer 属性还没有被创建

// DataManager 类包含一个名为 data 的存储属性, 初始值是一个空的字符串数组, 这里没有给出全部代码, 只需知道 DataManager 类的目的是管理和提供对这个字符串数组的访问即可

// DataManager 的一个功能是从文件导入数据. 该功能由 DataImporter 类提供, DataImporter 完成初始化需要消耗不少时间, 因为它的实例在初始化时可能需要打开文件,还要读取文件内容到内存

// DataManager 管理里数据时也可能不从文件中导入数据, 所以当 DataManager 实例被创建时,没必要创建一个 DataImporter 的实例,更明智的做法是第一次用到 DataImaporter 的时候才去创建它.

// 由于使用了 lazy ,importer 属性只有第一次被访问时才被创建,

// 例如

print(manager.importer.fileName)

// 注意 : 如果一个被标记为 lazy 的属性没有初始化时就同时被多个线程访问, 则无法保证该属性只被初始化一次

// 存储变量 和 实例变量

// OC 为类实例存储值和引用提供两种方法, 除了属性之外, 还可以使用实例变量择偶为属性值的后端存储

// Swift 编程语言中把这些理论统一起来用属性来实现, 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))

print("square.origin is now at (\(square.center.x), \(square.center.y))")

square.center = Point(x:15.0,y:15.0)

print("square.origin is now at (\(square.origin.x), \(square.origin.y))")

// 这个例子定义了3个结构体来描述几何形状

// 1: Point 封装另一个 (x, y) 的坐标

// 2: Size 封装了 一个 width 和 一个 height

// 3: Rect 表示一个有原点和尺寸的矩形

// Rect 也提供了一个名为 center 的计算属性, 一个矩形的中心点可以从原点 (origin) 和大小(size) 算出,所以不需要将它已显式声明的 Point 来保存, Rect 的计算属性 center 提供了自定义 getter 和 setter 来获取和设置矩形的中心点, 就像他有一个存储属性一样

// 上述例子中创建另一个名为 square 的 Rect 实例, 初始值原点是 (0,0), 宽度高度都是10,

// square 的 center 属性可以通过点运算符 (square.center) 来访问,这回调用该属性的 getter 方法来获取它的值,跟直接返回已经存在的值不同, getter 实际上通过计算然后返回一个新的 Point 来表示 suqare 的中心点

// center 属性之后被设置了一个新的值 (15,15), 表示向右上方移动正方形的位置. 设置属性 center 的值会调用他的 setter 方来修改 origin 的 x 和 y 的值, 从而实现移动正方形到新的位置

// 简化 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)

}

}

}

// 只读计算属性

// 只有 getter 方法, 没有 setter 方法的计算属性就是 '只读计算属性', 只读计算属性 总是返回一个值, 可以通过点运算符访问, 但不能设置新的值

// 注意 : 必须使用 var 关键字定义计算属性, 包括只读属性, 因为他们的值不是固定的, let 关键字只用来声明常量属性, 表示初始化后再也无法修改的值

// 只读计算属性 声明可以去掉 get 关键字 和 花括号

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("tah volume of this is \(fourByFiveByTwo.volume)")

// 属性观察器

// 属性观察器 监控和响应属性值的变化, 每次属性被设置新的值的时候都会调用属性观察器, 即使新值和当前值相同的时候也不例外

// 可以为除了延迟存储属性之外的其他存储属性添加属性观察器, 也可以通过重写属性的方式为继承的属性 (包括存储属性和计算属性) 添加属性观察器, 你不必为非重写的属性添加属性观察器, 因为可以通过他的 setter 直接监控和响应值的变化

// 可以为属性添加如下的一个或全部的观察器

// willSet 在新的值被设置之前调用

// didSet 在新的值被设置之后立即调用

// willSet 观察器会将新的属性值作为常量参数传入, 在 willSet 的实现代码中可以为这个参数指定一个名称, 如果不指定则参数仍然可用, 这是使用默认的 newValue 表示

// 同样. didSet 观察器将会将就旧的属性值作为参数传入, 可以为改参数命名或者使用默认的参数名 oldValue, 如果在 didSet 方法中再次对该属性赋值吗那么新值会覆盖旧值

// 注意 : 父类的属性在子类的构造器中被赋值时, 他在父类中的 willSet 和 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 stepCounter = StepCounter()

stepCounter.totalSteps = 200

stepCounter.totalSteps = 360

stepCounter.totalSteps = 600

// StepCounter 类定义另一个 Int 类型的属性, totalSteps. 它是一个存储属性, 包含 willSet 和 didSet 观察器

// 当 totalSteps 被设置新值的时候, 它的 willSet 和 didSet 观察器会被调用, 即使新值和当期值完全相同也会被调用

// 例子中的 willSet 观察器将表示新值的参数自定义为 newTotalSteps, 这个观察器只是简单的将新的值输出

// didSet 观察器在 totalSteps 的值改变后被调用, 它的新值和旧值进行对比, 如果总步数增加了就输出增加的步数, didSet 没有为旧值提供自定义名称, 所以默认 oldValue 表示旧值的参数名

// 注意 : 如果将属性通过 in-out 方式传入函数, willSet 和 didSet 也会被调用, 这是因为 in-out 参数采用了拷入拷出模式, 即在函数内部使用的是参数的 copy ,函数结束后, 又对参数重新复制.

// 全局变量 和 局部变量

// 计算属性 和 属性观察器所描述的功能也可以用于 全局变量 和 局部变量, 全局变量是在函数, 方法, 闭包或任何类型之外定义的变量, 局部变量是在函数, 方法, 或者 闭包 内部定义的变量

// 前面提到的全局 或 局部 变量都属于存储变量, 跟存储属性类似, 它为特定类型的值提供存储空间, 并允许读/写

// 另外, 在 全局或局部范围 都可以定义 计算型变量 和 为存储型变量定义 观察器, 计算性变量 和 计算属性一样, 返回一个计算结果而不是存储值, 声明格式也完全一样

// 注意 : 全局变量 或 常量 都是延迟计算的, 跟延迟存储属性相似, 不同的地方在于, 全局的常量或变量不需要标记 lazy 修饰符 , 而局部范围内的常量 和 变量从不延迟计算

// 类型属性

// 实例属性属于一个特定类型的实例, 每创建一个实例,实例就拥有一套自己的属性值,实例之间的属性相互独立, 也可以为类型本身定义属性, 无论创建了多少个该类型的实例,这些熟悉感都只有唯一一份, 这种属性就是类型属性

// 类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量 (就像 C语言的静态常量), 或者所有实例都能访问的一个变量

// 存储型类型属性可以是常量 或 变量, 计算型类型属性跟实例的计算睡醒一样只能是定义成变量

// 注意 : 跟实例的存储型属性不同, 必须给存储型类型属性指定默认值, 因为类型本身没有构造器, 也就无法再初始化过程中 使用 构造器给类型属性赋值

// 存储型类型属性是延迟初始化的, 它们只有在第一次被访问的时候才会被初始化, 即使它们被多个线程同时访问, 系统也保证只对其进行一次初始化, 并且不需要对其使用 lazy 修饰

// 类型属性语法

// 在 从 C语言 或 OC 中, 与某个类型关联的 静态常量 和 静态变量, 是作为全局 (global) 静态变量定义的, 但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的或括号内, 因此它的作用范围也就是在类型支持的范围内

// 使用关键字 static 来定义类型属性, 在为类定义计算型类型属性时, 可以改用关键字 class 来支持子类对父类的实现进行重写

struct SomeStructure{

static var storedTypeProperty = "Some value."

static var comnputedTypeProperty: 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)

SomeStructure.storedTypeProperty = "Anthor value."

print(SomeStructure.storedTypeProperty)

print(SomeEnumeration.computedTypeProperty)

print(SomeClass.computedTypeProperty)