swift08-属性

属性的简单使用介绍

Posted by catface on January 3, 2015

属性简介

  • 存储属性:存储常量(`let`)或变量(`var`),作为实例的一部分. 只能作用于类和结构体

  • 计算属性:计算一个值. 可用于类、结构体、枚举. 必须用 var 声明

  • 类型属性:上述两属性通常与特定类型的实例关联. 该属性可直接作用于类型本身

存储属性

  1. 案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     struct Range {
         var startIndex: Int // 变量存储属性
         let width: Int // 常量存储属性(创建实例时被初始化)
     }
    	
     var range = Range(startIndex: 0, width: 5)
     // 区间为 0...5
     range.startIndex = 3
     // 区间为 3...8
    
  2. 常量结构体的存储属性

    • 当值类型的实例被声明为常量时,其所有属性也成常量

    • 当引用类型的实例被声明为常量后,仍可修改该实例的变量属性

      1
      2
      3
      
        let rangeFix = Range(startIndex: 3, width: 8)
        rangeFix.startIndex = 1
        // 报错. 因结构体是值类型,而值类型的实例被声明为常量,其属性均成常量
      
  3. 延迟存储属性:lazy var声明. 第一次被调用时才计算其属性的初始化值

    • 使用场景

      • 当属性的值依赖于在实例的构造过程结束后才会知道具体值的外部因素时

      • 当获得属性的初始值需要复杂或大量计算时

    • 当被 lazy 标记的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
        class DataProvider {
            // 导入文件(耗时操作)
            var fileName = "data.txt"
        }
      		
        class DataManager {
            lazy var provider = DataProvider()
      		    
            // 数据管理
            var data = [String]()
        }
      		
        let manager = DataManager()
        manager.data.append("Some data")
        manager.data.append("Some more data")
        // DataProvider 实例还没有被创建
      		
      		
        print(manager.importer.fileName)
        // DataProvider 实例被创建(此时第一次被调用)
      
  4. 存储属性和实例变量

    • Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问

计算属性(-案例值得琢磨-)

  1. 不直接存储值,提供 gettersetter 来间接获取和设置值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
     // 定义中心坐标
     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 {
             // getter:返回 Point 实例(中心)
             get {
                 let centerX = origin.x + (size.width / 2)
                 let centerY = origin.y + (size.height / 2)
                 return Point(x: centerX, y: centerY)
             }
    
             // setter:设置 Point 中心坐标
             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 squareCenter = square.center
     // 中心坐标 (5, 5)
    	
     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)”
    
  2. 便捷 setter 声明

    • 若计算属性的 setter 没有定义新值的参数名,则可使用默认名称 newValue

      1
      2
      3
      4
      5
      
        // 上述代码的 setter 可简写
        set {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
      
  3. 只读计算属性:只有 getter 没有 setter 的计算属性,总是返回一个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     struct Cuboid {
         var width = 0.0, height = 0.0, depth = 0.0
    	
         // 只读计算属性
         var volume: Double {
             return width * height * depth
         }
     }
    	
     let rect1 = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
     print("the volume of rect1 is \(rect1.volume)")
    

属性观察器

  • 可为除延迟存储属性外的其他存储属性添加

  • 可通过重写属性的方式为继承的属性(存储属性、计算属性)添加

  • 父类的属性在子类构造器中被赋值时,其在父类的观察器会被调用

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

    • willSet:新值被设置前调用

    • didSet:新值被设置后调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
        class StepCounter {
            var totalSetps: Int = 0 {
                // newTotalSteps:新值(默认 newValue)
                willSet(newTotalSteps) {
                    print(newTotalSteps)
                } didSet {
                    // oldValue:旧值 
                    if totalSetps > oldValue {
                        print(totalSetps - oldValue)
                    }
                }
            }
        }
      		    
        let stepCounter = StepCounter()
        stepCounter.totalSetps = 50
        // 50
        // 50
      		
        stepCounter.totalSetps = 90
        // 90
        // 40
      		
        stepCounter.totalSetps = 80
        // 80
      
  • 若一个属性的 didSet 观察器里赋值,该值会替代之前设置的值

全局变量和局部变量

  1. 定义

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

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

  2. 注意

    • 全局的常量或变量都是延迟计算的(省略 lazy 修饰符)

    • 局部的常量或变量从不延迟计算

  3. 计算属性和属性观察器所描述的功能可用于全局变量和局部变量

类型属性(static):定义某个类型所有实例共享的数据

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

  • 必须给存储型类型属性指定默认值(无构造器,无法初始化时赋值)

  • 存储型类型属性是延迟初始化的(省略 lazy). 即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化

  1. 语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
     struct SomeStructure {
         // 存储型类型属性
         static var storedTypeProperty = "Some value."
         // 计算型类型属性(本例所用是只读的)
         static var computedTypeProperty: Int {
             return 5
         }
     }
     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 7
         }
    	
         // 为类定义计算型类型属性时,改用 class 支持子类对父类的实现进行重写
         class var overrideableComputedTypeProperty: Int {
             return 8
         }
     }
    
  2. 获取和设置类型属性的值

    • 和实例属性一样通过 .,但类型属性是通过类型本身来访问(是不是很像JAVA的静态类)

      SomeStructure.storedTypeProperty = "Another value."

  3. 案例:两个喇叭的音量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
     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 // 左声道音量为 7
     rightChannel.currentLevel = 11 // 右声道音量为 10(控制在音量上限内)