Swift学习】Swift编程之旅---属性(十四)

  属性关联特定类、结构或枚举的值,存储属性将存储常量和变量作为实例的一部分,计算属性用于计算一个值,而不进行存储。计算属性可以用于类、结构体和枚举里,存储属性只能用于类和结构体。存储属性和计算属性通常用于特定类型的实例,但是,属性也可以直接用于类型本身,这种属性称为类型属性。另外,还可以定义属性监视器来观察属性值的变化,以此来触发一个自定义的操作。属性监视器可以添加到存储属性上,也可以添加到从父类继承的属性。

  Stored Properties存储属性

  存储属性可以是常量或变量,你可以给存储属性设置默认值也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) 

rangeOfThreeItems.firstValue = 6 

FixedLengthRange的实例包含一个名为firstValue的变量存储属性和一个名为length的常量存储属性。在上面的例子中,length在创建实例的时候被赋值,因为它是一个常量存储属性,所以之后无法修改它的值。

  常量结构体的存储属性

  

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property

因为rangeOfFourItems声明成了常量,即使firstValue是一个变量属性,也无法再修改它了。这是由于结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。属于引用类型的类(class)则不一样,把一个引用类型的实例赋给一个常量后,仍然可以修改实例的变量属性。

  延迟存储属性

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

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

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

class DataImporter {
    /*
     DataImporter is a class to import data from an external file.
     The class is assumed to take a non-trivial amount of time to initialize.
     */
    var fileName = "data.txt"
    // the DataImporter class would provide data importing functionality here
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // the DataManager class would provide data management functionality here
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created

DataManager类包含一个名为data的存储属性,初始值是一个空的字符串(String)数组。DataManager类的目的是管理和提供对这个字符串数组的访问。

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

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

      

  计算属性

计算属性不直接存储值,而是提供一个 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 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。只读计算属性的声明可以去掉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" 

  属性观察

  观察属性值的变化,当发生变化时实现自定义操作。可以为属性添加如下的一个或全部监视器:

  • willSet在属性值变化前调用
  • didSet在属性值变化后立即调用

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

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

注意: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
// 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监视器里为属性赋值,这个值会替换监视器之前设置的值。

  类型属性

  实例属性是属于特定类型的实例的属性。每次创建该类型的新实例时,它都有它自己的一组属性值,与任何其他实例分离,也可以为类型本身定义属性,不管类型有多少个实例,这些属性都只有唯一一份。这种属性就是类型属性。

类型属性用于定义特定类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。对于值类型(指结构体和枚举)可以定义存储型和计算型类型属性,对于类(class)则只能定义计算型类型属性。

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

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

 

  类型属性语法

使用关键字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。

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。

AudioChannel也定义了一个名为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) 

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

rightChannel.currentLevel = 11
println(rightChannel.currentLevel)
// 输出 "10"
println(AudioChannel.maxInputLevelForAllChannels)  
时间: 2024-10-06 00:12:14

Swift学习】Swift编程之旅---属性(十四)的相关文章

#父与子的编程之旅#第十四章

对象 1.什么是对象?具有属性和方法(能够对对象做的操作)的某个实例可以称为对象.如,一个球,属性有球的颜色,球的大小,球的重量,方法有球的运动方向: 2.属性和方法.如1中所述,属性很好理解,就是能够描述对象特征的一些变量,方法可以理解为一些代码块,这些代码块可以对对象完成一些操作,和我们平时讲的函数有些相似: 3.什么是类?对象的描述(将要让这个对象做什么实现什么功能)或蓝图称为一个类: 4.创建类的一个实例.类的定义只是写了一个框架,将有什么样的属性,将要实现什么样的功能,都是空谈,只有使

Swift学习——Swift基础详解(四)

A:小儿编程很不好! B:多半是不爱学,从看英文版开始,让你爱上编程! Type Aliases    类型重定义(typedef) Swift中重定义类型的关键字是typealias,至于怎么用,应该不必多说了,看例子: typealias AudioSample = UInt16 //定义了一个类型名称AudioSample,代表UInt16类型 var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound is now 0 Boo

Swift学习——Swift基础详解(二)

上节说了没有营养的变量和常量,这玩意,都差不多,自己稍微看下就好了 Integers    整型 整数就是整数了,没有小数,整数有符号(+,-,0)或者无符号(0,+) Swift提供了8,16,32,64位的有符号和无符号的整数,命名使用C的方式,比如,8位无符号的整型UInt8,32位有符号的整型就是Int32 Integer Bounds    整型范围 可以使用min 和 max获取整数类型的最大值和最小值 let minValue = UInt8.min // minValue is

Swift学习——Swift基础详解(一)

注:由于基础部分在Swift Tour 中已经大体的说明了,所以在详解中不会达到100%的原文释义 Constants and Variables  常量和变量 常量和变量都需要声明名称和类型(作为程序员,这些基础也就不说了),常量一次赋值不能改变,变量的值可以改变 Declaring Constants and Variables   声明常量和变量 常量和变量在使用之前必须要声明,使用let关键字定义常量,var关键字定义变量 下面的例子可以用来定义用户登录的时候最大的尝试次数: let m

Swift学习——Swift基础详解(三)

小葵花课堂继续开讲 Numeric Literals    数字文本 数字文本有以下几种写法: A decimal number, with no prefix A binary number, with a 0b prefix An octal number, with a 0o prefix A hexadecimal number, with a 0x prefix 十进制数,无前缀:二进制数,0b前缀:八进制数,0o前缀:十六进制数,0x前缀 论数字17的N种表现形式: let deci

Swift学习——Swift基础详解(八)

Assertions    断言 可选可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况.然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能并不需要继续执行.这时,你可以在你的代码中触发一个断言(assertion)来结束代码运行并通过调试来找到值缺失的原因. Debugging with Assertions    使用断言进行调试 断言会在运行时判断一个逻辑条件是否为true.从字面意思来说,断言"断言"一个条件是否为真.你可以使用断言来保证在运行其他代

Swift学习——Swift基础详解(六)

Optionals    选配 选配(好像不是这么翻译的)适用于那些值可能为空的情况,一个选配有两种情况:存在值并且等于x,或者值不存在. 选配的概念在OC和C里面并没有,在OC中最接近的概念就是:OC中的返回值为对象的函数,当对象不存在的时候,可以返回nil,但是nil只能代表对象,不可以代表基础类型或者结构体和枚举类型.OC中使用NSNotFound表示值不存在.在Swift中,不需要使用其他的类型表示不存在的值. 举个例子: 在Swift中,String类型有一个方法叫做toInt,但是并

Swift学习——Swift基础详解(五)

上回书说道:灰常灰常基本的数据类型 下面咱们来点高级的: Tuples    元组 元组存储一对键值,并且没有类型限制 let http404Error = (404, "Not Found") // http404Error is of type (Int, String), and equals (404, "Not Found") 书上废话一堆,反正元组就是这么写,上面的例子还是(Int,String)类型的元组,而且元组里面的类型随便你定义 也可以将元组的变

Welcome to Swift (苹果官方Swift文档初译与注解十四)---90~93页(第二章)

Debugging with Assertions (断言调试) 断言是一个假设逻辑条件为真的运行时检查机制.在执行任意代码之前,可以使用断言来确认一个基本的条件情况.如果条件判断为真,代码将继续执行,如果条件为假,代码将会结束,应 用程序也会退出. 如果在调试环境中,运行的代码触发了一个断言(比如你在Xcode中编译并运行一个应用),你可以明确的看到错误的状态发生在哪里,并且查询应用程序在这个时刻的状态.断言也可以让 你使用适合的调测显示信息作为断言信息. 可以写一个全局函数assert来触发

Sql Server之旅——第十四站 深入的探讨锁机制

原文:Sql Server之旅--第十四站 深入的探讨锁机制 上一篇我只是做了一个堆表让大家初步的认识到锁的痉挛状态,但是在现实世界上并没有这么简单的事情,起码我的表不会没有索引对吧,,,还 有就是我的表一定会有很多的连接过来,10:1的读写,很多码农可能都会遇到类似神乎其神的死锁,卡住,读不出来,插不进入等等神仙的事情导致性 能低下,这篇我们一起来探讨下. 一: 当select遇到性能低下的update会怎么样? 1. 还是使用原始的person表,插入6条数据,由于是4000字节,所以两条数