IOS开发语言Swift入门连载---属性

IOS开发语言Swift入门连载—属性

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

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

  另外,还可以定义属性监视器来监控属性值的变化,以此来触发一个自定义的操作。属性监视器可以添加到自己写的存储属性上,也可以添加到从父类继承的属性上。

  

存储属性

  简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量,存储属性可以是变量存储属性(用关键字var 定义),也可以是常量存储属性(用关键字let 定义)。

  可以在定义存储属性的时候指定默认值,请参考构造过程一章的默认属性值一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考构造过程一章的在初始化阶段修改常量存储属性一节。

  下面的例子定义了一个名为FixedLengthRange 的结构体,它描述了一个在创建后无法修改值域宽度的区间:

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 在创建实例的时候被赋值,因为它是一个常量存储属性,所以之后无法修改它的值。  常量和存储属性  

如果创建了一个结构体的实例并赋值给一个常量,则无法修改实例的任何属性,即使定义了变量存储属性:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个变量属性,这里还是会报错

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

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

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

延迟存储属性

 

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

注意:  

必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。  

延迟属性很有用,当属性的值依赖于在实例的构造过程结束前无法知道具体值的外部因素时,或者当属性的值需要复杂或大量计算时,可以只在需要的时候来计算它。  

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

class DataImporter {
    /*
    DataImporter 是一个将外部文件中的数据导入的类。
    这个类的初始化会消耗不少时间。
    */
    var fileName = "data.txt"
    // 这是提供数据导入功能
}

class DataManager {
    @lazy var importer = DataImporter()
    var data = String[]()
    // 这是提供数据管理功能
}

let manager = DataManager()
manager.data += "Some data"
manager.data += "Some more data"
// DataImporter 实例的 importer 属性还没有被创建

  DataManager 类包含一个名为data 的存储属性,初始值是一个空的字符串(String )数组。虽然没有写出全部代码,DataManager 类的目的是管理和提供对这个字符串数组的访问。  

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

DataManager 也可能不从文件中导入数据。所以当DataManager 的实例被创建时,没必要创建一个DataManager 的实例,更明智的是当用到DataImporter 的时候才去创建它。  

由于使用了@lazy ,importer 属性只有在第一次被访问的时候才被创建。比如访问它的属性fileName 时:

println(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "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)
println("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 表示一个有原点和尺寸的矩形  

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

例子中接下来创建了一个名为square 的Rect 实例,初始值原点是(0, 0) ,宽度高度都是10 。如图所示蓝色正方形。  

square 的center 属性可以通过点运算符(square.center)来访问,这会调用 getter 来获取属性的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的Point来表示square 的中心点。如代码所示,它正确返回了中心点(5, 5) 。  

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 关键字定义计算属性,包括只读计算属性,因为他们的值不是固定的。var 关键字只用来声明常量属性,表示初始化后再也无法修改的值。

  注意:

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

  只读计算属性的声明可以去掉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)
println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 输出 "the volume of fourByFiveByTwo is 40.0"

  这个例子定义了一个名为Cuboid 的结构体,表示三维空间的立方体,包含width 、height 和depth 属性,还有一个名为volume 的只读计算属性用来返回立方体的体积。设置volume 的值毫无意义,因为通过width 、 height 和depth 就能算出volume 。然而,Cuboid 提供一个只读计算属性来让外部用户直接获取体积是很有用的。

  

属性监视器

  属性监视器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性监视器,甚至新的值和现在的值相同的时候也不例外。

  可以为除了延迟存储属性之外的其他存储属性添加属性监视器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性监视器。属性重载请参考继承一章的重载。

  注意:

  不需要为无法重载的计算属性添加属性监视器,因为可以通过 setter 直接监控和响应值的变化。

  可以为属性添加如下的一个或全部监视器:

  willSet 在设置新的值之前调用

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

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

  类似地,didSet 监视器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名oldValue 。

  注意:

   willSet 和didSet 监视器在属性初始化过程中不会被调用,他们只会当属性的值在初始化之外的地方被设置时被调用。

  注意:

   willSet 和didSet 监视器在属性初始化过程中不会被调用,它们只会当属性的值在初始化之外的地方被设置时被调用。

  这里是一个willSet 和didSet 的实际例子,其中定义了一个名为StepCounter 的类,用来统计当人步行时的总步数,可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。

class StepCounter {
    var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        println("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            println("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

  StepCounter类定义了一个Int 类型的属性totalSteps ,它是一个存储属性,包含willSet 和didSet 监视器。

  当totalSteps 设置新值的时候,它的willSet 和didSet 监视器都会被调用,甚至当新的值和现在的值完全相同也会调用。

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

  didSet监视器在totalSteps 的值改变后被调用,它把新的值和旧的值进行对比,如果总的步数增加了,就输出一个消息表示增加了多少步。didSet 没有提供自定义名称,所以默认值oldValue 表示旧值的参数名。

  注意:

  如果在didSet 监视器里为属性赋值,这个值会替换监视器之前设置的值。

  

全局变量和局部变量

  计算属性和属性监视器所描述的模式也可以用于全局变量和局部变量,全局变量是在函数、方法、闭包或任何类型之外定义的变量,局部变量是在函数、方法或闭包内部定义的变量。

  前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它提供特定类型的存储空间,并允许读取和写入。

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

  注意:

  全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记@lazy 特性。

  局部范围的常量或变量不会延迟计算。

  

类型属性

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

  也可以为类型本身定义属性,不管类型有多少个实例,这些属性都只有唯一一份。这种属性就是类型属性。

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

  对于值类型(指结构体和枚举)可以定义存储型和计算型类型属性,对于类(class)则只能定义计算型类型属性。

  值类型的存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算属性一样定义成变量属性。

  注意:

  跟实例的存储属性不同,必须给存储型类型属性指定默认值,因为类型本身无法在初始化过程中使用构造器给类型属性赋值。

 

类型属性语法

  在 C 或 Objective-C 中,静态常量和静态变量的定义是通过特定类型加上global 关键字。在 Swift 编程语言中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。

  使用关键字static 来定义值类型的类型属性,关键字class 来为类(class)定义类型属性。下面的例子演示了存储型和计算型类型属性的语法:

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
    // 这里返回一个 Int 值
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
    // 这里返回一个 Int 值
    }
}
class SomeClass {
    class var computedTypeProperty: Int {
    // 这里返回一个 Int 值
    }
}

  注意:

  例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。

  获取和设置类型属性的值

  跟实例的属性一样,类型属性的访问也是通过点运算符来进行,但是,类型属性是通过类型本身来获取和设置,而不是通过实例。比如:

println(SomeClass.computedTypeProperty)
// 输出 "42"

println(SomeStructure.storedTypeProperty)
// 输出 "Some value."
SomeStructure.storedTypeProperty = "Another value."
println(SomeStructure.storedTypeProperty)
// 输出 "Another value.”

  下面的例子定义了一个结构体,使用两个存储型类型属性来表示多个声道的声音电平值,每个声道有一个 0 到 10 之间的整数表示声音电平值。

  后面的图表展示了如何联合使用两个声道来表示一个立体声的声音电平值。当声道的电平值是 0,没有一个灯会亮;当声道的电平值是 10,所有灯点亮。本图中,左声道的电平是 9,右声道的电平是 7。

  上面所描述的声道模型使用AudioChannel 结构体来表示:

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
        }
    }
    }
}

  结构AudioChannel 定义了 2 个存储型类型属性来实现上述功能。第一个是thresholdLevel ,表示声音电平的最大上限阈值,它是一个取值为 10 的常量,对所有实例都可见,如果声音电平高于 10,则取最大上限值 10(见后面描述)。

  第二个类型属性是变量存储型属性maxInputLevelForAllChannels ,它用来表示所有AudioChannel 实例的电平值的最大值,初始值是 0。

  也定义了一个名为currentLevel 的实例存储属性,表示当前声道现在的电平值,取值为 0 到 10。

  属性currentLevel 包含didSet 属性监视器来检查每次新设置后的属性值,有如下两个检查:

  如果currentLevel 的新值大于允许的阈值thresholdLevel ,属性监视器将currentLevel 的值限定为阈值thresholdLevel 。

  如果修正后的currentLevel 值大于任何之前任意 AudioChannel 实例中的值,属性监视器将新值保存在静态属性maxInputLevelForAllChannels 中。

  注意:

  在第一个检查过程中,didSet 属性监视器将currentLevel 设置成了不同的值,但这时不会再次调用属性监视器。

  可以使用结构体AudioChannel 来创建表示立体声系统的两个声道leftChannel 和rightChannel :

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

  如果将左声道的电平设置成 7,类型属性maxInputLevelForAllChannels 也会更新成 7:

leftChannel.currentLevel = 7
println(leftChannel.currentLevel)
// 输出 "7"
println(AudioChannel.maxInputLevelForAllChannels)
// 输出 "7"

  如果试图将右声道的电平设置成 11,则会将右声道的currentLevel 修正到最大值 10,同时maxInputLevelForAllChannels 的值也会更新到 10:

rightChannel.currentLevel = 11
println(rightChannel.currentLevel)
// 输出 "10"
println(AudioChannel.maxInputLevelForAllChannels)
// 输出 "10"
时间: 2024-10-16 00:07:41

IOS开发语言Swift入门连载---属性的相关文章

IOS开发语言Swift入门连载---可选链

IOS开发语言Swift入门连载-可选链 可选链(Optional Chaining) 是一种可以请求和调用属性.方法及下标脚本的过程,它的可选性体现于请求或调用的目标当前可能为空(nil ).如果可选的目标有值,那么调用就会成功:相反,如果选择的目标为空(nil ),则这种调用将返回空(nil ).多次请求或调用可以被链接在一起形成一个链,如果任何一个节点为空(nil )将导致整个链失效. 注意: 的可选链和 Objective-C 中的消息为空有些相像,但是 Swift 可以使用在任意类型中

IOS开发语言Swift入门连载---类型转换

IOS开发语言Swift入门连载-类型转换 类型转换可以判断实例的类型,也可以将实例看做是其父类或者子类的实例. 类型转换在 Swift 中使用is 和 as 操作符实现.这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型. 你也可以用来检查一个类是否实现了某个协议,就像在 Checking for Protocol Conformance部分讲述的一样. 定义一个类层次作为例子 你可以将它用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的

IOS开发语言Swift入门连载---类和结构体

IOS开发语言Swift入门连载-类和结构体 类和结构体是人们构建代码所用的一种通用且灵活的构造体.为了在类和结构体中实现各种功能,我们必须要严格按照常量.变量以及函数所规定的语法规则来定义属性和添加方法. 与其他编程语言所不同的是,Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件.你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口. 注意: 通常一个类 的实例被称为对象 .然而在Swift 中,类和结构体的关系要比在其他语言中更加的密切,本

IOS开发语言Swift入门连载---闭包

IOS开发语言Swift入门连载-闭包 闭包是自包含的函数代码块,可以在代码中被传递和使用. Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似. 闭包可以捕获和存储其所在上下文中任意常量和变量的引用. 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包.Swift 会为您管理在捕获过程中涉及到的所有内存操作. 注意: 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在 值捕获 章节对其进行详细了

IOS开发语言Swift入门连载---枚举

IOS开发语言Swift入门连载-枚举 枚举定义了一个通用类型的一组相关的值,使你可以在你的代码中以一个安全的方式来使用这些值. 如果你熟悉 C 语言,你就会知道,在 C 语言中枚举指定相关名称为一组整型值.Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值.如果一个值(被认为是"原始"值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或是一个整型值或浮点值. 此外,枚举成员可以指定任何类型的相关值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(v

IOS开发语言Swift入门连载---嵌套类型

IOS开发语言Swift入门连载-嵌套类型 枚举类型常被用于实现特定类或结构体的功能.也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型.类和结构体中定义支持嵌套的类型. 要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套. 嵌套类型实例 下面这个例子定义了一个结构体BlackjackCard (二十一点),用来模拟BlackjackCard 中的扑克牌点数.Bl

IOS开发语言Swift入门连载---方法

IOS开发语言Swift入门连载-方法 方法是与某些特定类型相关联的函数.类.结构体.枚举都可以定义实例方法:实例方法为给定类型的实例封装了具体的任务与功能.类.结构体.枚举也可以定义类型方法:类型方法与类型本身相关联.类型方法与 Objective-C 中的类方法(class methods)相似. 结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一.在 Objective-C 中,类是唯一能定义方法的类型.但在 Swift 中,你不仅能选择是否要定义一个类

IOS开发语言Swift入门连载---下标脚本

IOS开发语言Swift入门连载-下标脚本 下标脚本 可以定义在类(Class).结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问对象.集合或序列的快捷方式,不需要再调用实例的特定的赋值和访问方法.举例来说,用下标脚本访问一个数组(Array)实例中的元素可以这样写someArray[index] ,访问字典(Dictionary)实例中的元素可以这样写 someDictionary[key] . 对于同一个目标可以定义多个下标脚本,通过索引值类型的不同来进

IOS开发语言Swift入门连载---继承

IOS开发语言Swift入门连载-继承 一个类可以继承(inherit)另一个类的方法(methods),属性(property)和其它特性.当一个类继承其它类时,继承类叫子类(subclass),被继承类叫超类(或父类,superclass).在 Swift 中,继承是区分「类」与其它类型的一个基本特征. 在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本(subscripts),并且可以重写(override)这些方法,属性和下标脚本来优化或修改它们的行为.Swift 会检查你的