Swift-属性

1. 存储属性

  • 一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。
  • 可以是变量存储属性(用关键字 var 定义)。
  • 也可以是常量存储属性(用关键字 let 定义)。
struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)  // 该区间表示整数 0,1,2
rangeOfThreeItems.firstValue = 6    // 该区间现在表示整数 6,7,8

FixedLengthRange 的实例包含一个名为 firstValue 的变量存储属性和一个名为 length 的常量存储属性。
length 在创建实例的时候被初始化,且之后无法修改它的值,因为它是一个常量存储属性。

1.1 常量结构体实例的存储属性

  • 如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性。
  • 即使被声明为可变属性也不行。
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)   // 该区间表示整数 0,1,2,3
rangeOfFourItems.firstValue = 6 // 尽管 firstValue 是个可变属性,但这里还是会报错

因为 rangeOfFourItems 被声明成了常量(用 let 关键字),所以即使 firstValue 是一个可变属性,也无法再修改它了。

1.2 延时加载存储属性

  • 指当第一次被调用的时候才会计算其初始值的属性。
  • 在属性声明前使用 lazy 来标示一个延时加载存储属性。
  • 必须将延时加载属性声明成变量(使用 var 关键字)。(因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。)
class DataImporter {
    /*
    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 属性还没有被创建
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出“data.txt”
注意:
    如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。

1.3 存储属性和实例变量

  • 如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
  • Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的备份存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。

2. 计算属性

  • 除存储属性外,类、结构体和枚举可以定义计算属性。
  • 计算属性不直接存储值,而是提供一个 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))")
// 打印“square.origin is now at (10.0, 10.0)”

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

Point 封装了一个 (x, y) 的坐标
Size 封装了一个 width 和一个 height
Rect 表示一个有原点和尺寸的矩形

2.1 简化 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)
        }
    }
}

2.2 简化 Getter 声明

  • 如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果。
struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

在 getter 中忽略 return 与在函数中忽略 return 的规则相同。

2.3 只读计算属性

  • 只有 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("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印“the volume of fourByFiveByTwo is 40.0”

定义了一个名为 Cuboid 的结构体,表示三维空间的立方体,包含 width、height 和 depth 属性。
结构体还有一个名为 volume 的只读计算属性用来返回立方体的体积。
为 volume 提供 setter 毫无意义,因为无法确定如何修改 width、height 和 depth 三者的值来匹配新的 volume。然而,Cuboid 提供一个只读计算属性来让外部用户直接获取体积是很有用的。

3. 属性观察器

  • 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
  • 可以为属性添加其中一个或两个观察器:
    • willSet 在新的值被设置之前调用:
      • willSet 观察器会将新的属性值作为常量参数传入,在 willSet 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue 表示。
    • didSet 在新的值被设置之后调用
      • didSet 观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 oldValue。如果在 didSet 方法中再次对该属性赋值,那么新值会覆盖旧的值。
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("将 totalSteps 的值设置为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
注意:
    1. 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 willSet 和 didSet 观察器。
    2. 而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。

StepCounter 类定义了一个叫 totalSteps 的 Int 类型的属性。它是一个存储属性,包含 willSet 和 didSet 观察器。
当 totalSteps 被设置新值的时候,它的 willSet 和 didSet 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
例子中的 willSet 观察器将表示新值的参数自定义为 newTotalSteps,这个观察器只是简单的将新的值输出。
didSet 观察器在 totalSteps 的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。didSet 没有为旧值提供自定义名称,所以默认值 oldValue 表示旧值的参数名。

注意:
    1. 如果将带有观察器的属性通过 in-out 方式传入函数,willSet 和 didSet 也会调用。
    2. 这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。

4. 全局变量和局部变量

  • 全局变量是在函数、方法、闭包或任何类型之外定义的变量。

  • 局部变量是在函数、方法或闭包内部定义的变量。

  • 计算属性和观察属性所描述的功能也可以用于全局变量和局部变量。

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

5. 类型属性

  • 实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。

  • 你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。

      注意:
      1. 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
      2. 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 lazy 修饰符。
    

5.1 类型属性语法

  • 在 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 overrideableComputedTypeProperty: Int {
        return 107
    }
}
注意
    例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。

5.2 获取和设置类型属性的值

  • 跟实例属性一样,类型属性也是通过点运算符来访问。
  • 但是,类型属性是通过类型本身来访问,而不是通过实例。
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”

struct AudioChannel {
    static let thresholdLevel = 10  //  音量的最大上限阈值
    static var maxInputLevelForAllChannels = 0  //  表示所有 AudioChannel 实例的最大输入音量
    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”
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容