属性(Properties)

376 阅读24分钟

原文

属性将值和一个特定的类,结构体或者枚举联系在一起。存储属性存储常量或者变量值作为实例的一部分,而机选属性计算(而不是存储)一个值。计算属性由类,结构体,和枚举提供。存储属性只有类和结构体提供。

除此之外,你可以定义属性观察者来监听属性值的改变,你可以对这些变化用自定义的操作进行响应。属性观察者可以加给你自己定义的存储属性上,也可以是子类从父类继承来的属性。

你也可以使用属性包装来复用多个属性的getter和setter中的代码。

存储属性(Stored Properties)

在它简单的形式中,存储属性是一个常量或者变量作为一个特殊类或者结构体的实例的一部分来存储。存储属性可以是可变存储属性(使用var关键字说明)或者不可变存储属性(使用let关键字说明)。

你可以为存储属性提供一个默认的值作为它定义的一部分。像在Default Property Values中描述的一样。你也可以在初始化的时候给存储属性初始化的时候设置或者修改初始化值。对常量存储属性也是可以的,像Assigning Constant Properties During Initialization中描述的。

下面的例子定义了一个名为FixedLengthRange的结构体,描述了一个整型的区间范围,它的区间长度在创建之后不可以更改:

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

FixedLengthRange的实例有一个可变的存储属性名为firstValue和一个常量存储属性名为length。在上面的例子中,length在新range创建的时候初始化并且之后不能修改,因为它是常量属性。

常量结构体实例的存储属性(Stored Properties of Constant Structure Instances)

如果你创建了一个结构体的实例并且将这个结构体分配给一个常量,你不能改变实例的属性,即使他们声明为了可变属性:

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声明为了一个常量(使用let关键字),不可能修改他的firstValue属性,即使firstValue是一个可变属性。

这个表现是因为结构体是值类型。当值类型的实例标记为常量时,他的全部属性也是常量。

同样的对于类来说不是这样的,它是引用类型。如果你分配了一个引用类型给常量,你可以改变实例的可变属性。

懒存储属性(Lazy Stored Properties)

懒存储属性是知道第一次使用才会计算初始值的属性。通过在声明前面写lazy修饰词指明一个懒加载存储属性。

必需声明一个可变的懒加载属性(使用var关键词),因为它的初始值直到实例初始化完成之后可能没有获取。常量属性在初始化完成之前一定要有一个值,所以不能声明为懒加载。

当一个属性的初始化值依赖直到实例初始化完成之后才知道值的外在因素时懒加载属性非常有用。当属性的初始化值需要用除非或者直到必要的时候才执行的复杂的或者高昂的计算来设置时懒加载也非常有用。

下面的例子使用了懒加载存储属性来避免非必要的复杂的类的初始化。这个例子定义了两个名为DataImporter和DataManager的类,两个都没有完全显示:

class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a nontrivial 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类的目的是管理和提供对这个String数据的数组的访问。

DataManager类的功能的一部分是从一个文件中导入数据的功能。这个功能有DataImpoter类提供,假设用大量的时间来初始化。这可能因为DataImporter实例需要打开一个文件并且当DataImporter实例初始化的时候将他的内容读到内存中。

对DataManager实例来说可能没有从文件中导入数据时管理它的数据,所以没有必要当DataManager创建的时候来创建一个新的DataImporter实例。取而代之的是,如果并当它第一次使用的时候创建DataImporter实例更有意义。

因为它用lazy修饰词标记了,importer属性的DataImporter实例只有在importer属性第一次访问的时候创建,例如查看它的filename属性的时候:

print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"

如果标记了lazy修饰词的属性通过多线程同时访问并且属性没有被初始化,不能保证属性只初始化一次。

存储属性和实例变量(Stored Properties and Instance Variables)

如果你使用过Objective-C,你可能知道有两种作为类实例一部分的方式来存储值和引用。除了属性,你可以使用实例变量来作为属性中存储的值的备份。

swift将这些概念整合到了单独的一个属性声明中。swift属性没有对应的实例变量,属性的备份存储不是直接访问的。这个方式避免了在不同上下文中值如何访问的疑惑并且将属性的声明简化为单一的,最佳的语句。关于属性全部的信息--包括他的名字,类型,和内存管理特点--定义在单一的作为类型定义一部分的位置。

计算属性(Computed Propertied)

除了存储属性,类,结构体,和枚举可以定义计算属性,实际上没有存储值。取而代之的是,他们提供了一个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))")
// Prints "square.origin is now at (10.0, 10.0)"}

例子定义了三个结构体来使用几何形状:

  • point封装了点的x和y坐标
  • size封装了width和height
  • rect用原点和尺寸定义了一个矩形

rect结构体也提供了一个名为center的计算属性。rect当前的中点可以从他的origin和size中确定,所以你不需要把中点存储为一个确定的Point值。取而代之的是,Rect给名为center的计算变量定了一个自定义的getter和setter,来让你可以向它是一个真实的存储值一样使用矩形的center。

上面的例子创建了一个新的rect变量名为square。变量square用原点(0,0)和10为宽高来初始化。这个square在下面的图表中用蓝色正方形表示。

square变量的center属性通过点语法(square.center)来访问,使center的getter被调用,来索取当前属性的值。不是返回一个已经存在的值,getter实际上计算和返回以个新的Point来表示正方形的中心。像上面能看到的,getter正确的返回了一个(5,5)的中心点。

center属性之后设置为新的值(15,15),将方形向上和右移动,到了下面图表中橙色正方形展示的新位置。设置center属性调用center的setter,改变了存储属性origin的x和y值,将正方形移到了新的位置。


简写Setter声明(Shorthand Setter Declaration)

如果计算属性的setter没有为要设置的新值定义名称,会使用newValue默认名称。这里是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声明(Shorthand Getter Declaration)

如果getter的整个主体是一个单独的表达式,getter隐式返回那个表达式。这里关于Rect结构体的另一个版本使用了这个简写符号和setter的简写符号:

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遵从一样的规则,像在Functions With an Implicit Return中描述的一样。

只读计算属性(Read-Only Computed Properties)

一个有getter的计算属性没有setter就是read-only计算属性。一个只读的只读属性通常返回一个值,可以用点语法访问,但是不能设置为不同的值。

必需使用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)")
// Prints "the volume of fourByFiveByTwo is 40.0"

这个例子定义了一个新的名为cuboid的结构体,用width,height和depth属性表示一个3D矩形盒子。结构体也有只读属性名为volume,计算和返回当前的cuboid的体积。将volume设置为settable没有意义,因为会对为特殊的volume值用哪些width,height,和depth产生困惑。尽管如此,提供一个只读计算属性使其他的使用者发现它当前计算的体积对cuboid来说有用。

属性观察者(Property Observers)

属性观察者观察和对在属性中的值的改变进行响应。属性观察者每次属性的值设置的时候调用,即使新的值和当前值一样。

你可以对你定义的任何存储属性添加属性观察者,除了懒加载存储属性。你也可以通过在子类中重写属性给任何继承的属性添加属性观察者(无论是存储至或者计算值)。不需要为非重写的计算属性定义属性观察者,因为你可以在计算属性的setter中观察和响应他们值的改变。属性重写在Overriding描述。

你可以选择在一个属性上定义一个后者两个观察者:

  • willSet在值存储之前调用
  • didSet在新值存储之后立即调用

如果你实现了一个willSet观察者,将新值作为一个常量参数传递。你可以在willSet实现中制定这个参数的名字。如果在你的实现中没有写参数名字和括号,可以使用参数的默认参数名newValue获取参数。

相似的,如果你实现didSet观察者,传递一个包含旧属性值的常量参数。你可以命名参数或者使用oldValue默认参数名。如果你在它自己didSet观察者中给属性分配了一个值,你分配的值替代之前设置的。

父类属性的willSet和didSet观察者当属性在子类初始化设置的时候调用,在父类初始化方法调用之后。当一个类设置他自己的属性时他们不会调用,在父类初始化调用之前。

这里是willSet和didSet的一个例子。上面的例子定义了一个新的名为StepCounter的类,追踪人在走路时步数的数目。这个类可能用从步数器或者其他步数技术器中输入的数据来保持追踪一个人日常程序的锻炼。

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观察者的存储属性。

totalSetps的willSet和didSet观察者在属性分配新值得时候调用。即使新值和现在的值一样也会如此。

这个例子的willSet观察者为将要传进来的新值使用一个自定义名字newTotalSteps。在这个例子中,他只是把将要设置的值打印出来。

didSet观察者在totalSteps更新之后调用。将totalSteps的新值和旧值进行对比。如果步数的总数增加了,打印一条信息来指定增加了多少新的步数。didSet观察者没有给旧值提供自定义参数名称,取而代止使用了oldValue的默认值。

如果你穿了一个有观察者的属性给一个函数作为in-out参数,willSet和didSet观察者会经常调用。这是因为in-out参数的copy-in copy-out内存模型。在函数的结尾值都会写会到属性中。关于in-out参数的表现细节的讨论,查看In-Out Parameters

属性包装(Property Wrappers)

属性包装在代码之间增加了一个分隔的管理属性如何存储和定义属性的代码的层。例如,如果你的属性提供线程安全检查或者存储他们数据库中内在的数据,你不得不在每个属性中写这些代码。当你使用属性打包时,当你定义包装的时候只写一次管理的代码,然后通过把它用到多个属性中来复用管理代码。

为了定义一个属性包装,你写一个定义一个wrappedValue属性的结构体,枚举或者类。在下面的代码中,结构体TwelveOrLess结构体确保他包装的值总是包含一个小于或者等于12的数字。如果你让他存储一个较大的数字,它会存储12.

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

setter确保新值小于12,getter返回存储的值。

上面例子中number的声明把变量表为了private,确保number只在TwelveOrless的实现中使用。写在其他任何位置的代码使用getter和setter访问wrappedValue的值,不能直接使用number。关于private的信息,查看Access Control

通过在属性前作为attribute写上wrapper的名字对一个属性应用wrapper。这里是一个存储小矩形的结构体,使用相同的“small”的定义,它通过TwelveOrLess属性包装来实现的:

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"

属性height和width从TwelveOrLess定义中获取他们初始的值,将TwelveOrLess.number的值设置为0。成功的数字10存储到rectangle.height中因为它是一个小数。尝试存储25实际替代存了12,因为25对属性setter的规则来说太大了。

当你对属性使用封装时,编译器同步给包装提供存储的代码和通过包装提供对属性访问的代码。(属性包装负责存储包装的值,所以没有关于它的同步带吗)。你可以用属性包装的表现来写代码,不用采用特殊的属性语法。例如,这里是之前的将属性明确包装在TwelveOrLess结构体中的代码中的SmallRectangle的一个版本,用attribute代替了@TwelveOrless。

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

_height和_width属性存储了一个property wrapper的实例,TwelveOrLess。height和width的getter和setter包装了对wrappedValue属性的访问。

为包装属性设置初始值(Setting Initial Values for Wrapped Properties)

在上面例子中的代码通过在TwelveOrLess定义中给number一个初始值来为包装属性设置初始值。使用属性包装的代码,不能为由TwelveOrLess包装的属性指定不同的初始化值--例如,SmallRectangle的定义不能给height或者width初始化值。为了可以设置初始化值或则其他的自定义,属性包装需要增加一个初始化方法。这里是TwelveOrLess的名为SmallNumber的扩展版本,定义了可以设置被包装的和最大的值的初始化:

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

SmallNumber的定义包括三个初始化方法--init(),init(wrappedValue:),和init(wrappedValue:maximum:)--下面的例子用来设置包装的值和最大的值。关于initialization和initializer语法的信息,查看Initialization

当你给一个属性使用了wrapper并且你没有指定初始化值的时候,swift使用init()初始化方法设置wrapper。例如:

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"

包装height和width的SmallNumber实例通过调用SmallNumber()来创建。初始化方法中的代码设置初始包装值和初始最大值,使用默认值0和12.属性包装任然提供全部的初始化值,像之前使用在SmallRectangle中的TwelveOrLess。不像那个例子,SmallNumber也支持把写这些值作为声明属性的一部分。

当你为属性指定一个初始值的时候,swift使用init(wrappedValue:)初始化方法来设置wrapper。例如:

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"

当你在有一个wrapper的属性上写=1的时候,那会理解为调用init(wrappedValue:)初始化方法。包装了height和width的SmallNumber实例通过调用SmallNumber(wrappedValue:1)来创建。初始化方法使用在这里指定的wrapped value,并且用了默认的最大值12.

当你在自定义属性后面的括号中写参数的时候,swift使用接受这些参数的初始化方法来设置wrapper。例如,如果你提供了一个初始化值和一个最大值,swift使用init(wrappedValue:maximum:)初始化方法:

struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"

包装height的SmallNumber实例通过调用SmallNumber(wrappedValue:2,maximum:5)创建,包装width的实例通过调用SmallNumber(wrappedValue:3,maximum:4)来创建。

通过在属性包装中加入参数,当它创建的时候你可以在wrapper中设置初始化状态或者给wrapper传递另一个选项。这的语法是使用属性包装最通常的方式。

当你包括属性包装参数的时候,你也可以通过分配指定一个初始化值。swift将分配当做一个wrappedValue参数并且使用你包含的接受参数的初始化方法。例如:

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"

包装height的SmallNumber的实例通过调用SmallNumber(wrappedValue:1)来创建,使用默认最大值12。包装width的实例通过调用SmallNumber(wrappedValue:2,maximum:9)来创建。

从属性包装来表示一个值(Projectiong a Value From a Property Wrapper)

除了wrapped value,属性包装可以通过定义一个projected value来展示额外的功能--例如,管理数据库的一个属性包装可以在它的projected value上展示flushDatabaseConnection()方法。projected value的名字和wrapped value的名字一样,除了它用用美元符号开始($)。因为你的代码不能定义用$开头的属性,projected value不会和你定义的属性冲突。

在上面的SmallNumber例子中,如果你尝试设置一个太大的数字给属性,属性包装在存储它之前进行调整。下面的代码给SmallNumber结构体增加了一个projectedValue属性来保持追踪属性包装是否在存储新值之前为舒心调整了新值。

@propertyWrapper
struct SmallNumber {
    private var number = 0
    var projectedValue = false
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

用s.$someNumber访问wrapper的projected value。在储存了一个像4的小数字后,s$someNumber的值是falese。不过,projected value在尝试存储一个像55过大的值后会是true。

一个包装属性可以用它的projected value返回任何类型的值,属性包装只表示一部分信息--数字是否被修改了--所以它用它的projected value来表示布尔值。一个需要表达更多信息的wrapper可以返回其他数据类型的实例,或者他可以作为他的projected value返回他自己来表示wrapper 的实例。

当你从类型的部分的代码中访问一个projected值的时候,像一个属性getter 或者一个实例方法,你可以用$height和$width忽略height和width周附近的wrapper的属性名字前的self.:

enum Size {
    case small, large
}

struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}

因为属性包装语法只是属性的getter和setter的语法糖,访问height和width表现得像访问其他属性一样。如果你调用resize(to:.large),switch的.largecase将举行的height和width设置为100.wrapper防止这些属性的值大于12,并把projected value设置为true,来记录它修改了他们的值的事实。在resize(to:)的最后,返回语句检查$height和$width来判断属性包装是否调整了height或者width。

全局和本地变量(Golbal and Local Varibles)

上面描述的对于计算和观察属性的特性对全局变量和本地变量也是可用的。定义在任何函数,方法,闭包,或者类上下文之外的全局变量是可变的。定义在函数,方法,或者闭包上下文中的本地变量是可变的。

在之前章节遇到的全局和本地变量全都存成可变的。存储比那辆像存储属性,为一个明确的类型的值提供存储并且允许值可以设置和获取。

不过,你也可以定义计算变量和为存储变量定义观察者,在全局或者本地范围中。计算变量计算他们的值,而不是存储,他们作为计算属性用一样的方式书写。

全局常量和变量通常是懒加载的,用Lazy Stored Properties类似的方式。不想懒加载存储属性,全局常量和变量不需要用lazy修饰词标记。本地常量和变量不会懒计算。

类型属性(Type Properties)

实例属性是属于一个特定类型的实例的属性。每次你创建一个那个类型的实例,他有自己的属性值设置,和其他任何实例分隔的。

你也可以定义术语类型自己的属性,不是对那个类型的任何一个实例。将会只有这些属性的一份拷贝,不管你创建了多少个这个类型的实例。这一类的属性成为类型属性。

类型属性对于定义对于一个类型全部实例都一直的值很有用,例如一个全部实例都可以使用的常量属性(像C中的静态常量),或者一个存储了对那个类型的全部实例来说是全局的值的可变属性(像C中的静态变量)。

存储类型属性可以常量或者变量。计算类型属性通常声明为可变属性,和计算实例属性一样的方式。

不想存储实例变量,必须给存储类型的类型属性一个默认值。因为类自己没有可以在初始化的时候给存储类型属性分配值的初始化值。存储的类属性在他们第一次访问的时候懒初始化。他们保证只初始化一次,即使被多个线程同时访问,他们不需要用lazy修饰词标记。

类型属性语法(Type Property Syntax)

在C和Objective-C中,关联一个类型作为全局静态变量定义静态常量或者变量。不过,在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
    }
}

上面计算的类型属性的例子是只读计算类型属性,但是你可以用计算实例属性一样的语法定义读写的计算类型属性。

查找和设置类型属性(Querying and Setting Type Properties)

类型属性使用点语法查找和设置,就像实例属性一样。不过,类型属性在类型上进行查找和设置,不是类型的实例。例如:

print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"

下面的例子使用了两个存储类型属性作为一个结构体的一部分,结构体为一些录音频道模型了录音等级仪表。每一个频道有一个在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 {
                // cap the new audio level to the threshold level
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // store this as the new overall maximum input level
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

AudioChannel结构体定义两个存储类型的属性来表示他的功能。首先,thresholdLevel,定义一个音频等级可以设置的最大值。这是一个对全部AudioChannel实例都是10的常量值,如果一个音频信号是一个比10大的值,他会被减为阈值(像下面描述的)。

第二了类型属性是一个名为maxInputLevelForAllChannels的可变存储属性。保持已经被全部AudioChannel实例接受了的最大输入值。开始是一个初始值0。

AudioChannel结构体也定义了一个名为currentLevel的存储实例属性,在0到10范围中表示频道当前音频等级。

currentLevel属性有一个didSet在任何设置它的时候属性观察者来查看currentLevel的值。这个观察者执行两个检查:

  • 如果currentLevel的新值比可允许的thresholdLevel大,属性观察者将currentLevel减成thresholdLevel。
  • 如果currentLevel(裁剪后的值)的新值比任何之前AudioChannel实例接受的值大,属性观察者存储新的currentLevel值在maxInputLevelForAllChannels类型属性中。
这两个检查中的第一个,didSet观察者给currentLevel设置了不同的值。不过,这没有引起再次调用观察者

你可以使用AudioChannel结构体来创建两个名为leftChannel和rightChannel的新的音频频道,来表示一个立体声新系统的音频等级:

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

如果你将legt频道的currentLevel设置为7,你可以看到maxInputLevelForAllChannels类型属性变成了7:

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"

如果你将right频道的currentLevel设置为11,你可以看到right频道的currentLevel属性建委最大值10,并且maxInputLevelForAllChannels类型属性更新为10:

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"