类与结构体
Swift中,并不要求把自定义类或结构的接口和实现写在不同的文件中。你在一个文件中定义类或结构体,那么这个类或结构体的外部接口就自动可以在其他代码中使用了。
类和结构有很多相似和区别,相同点有:
》可以定义属性来存储值
》可以定义方法提供各种功能
》可以定义下标使得可以通过下标语法获取值
》可以定义初始化方法来设定初始状态
》可以被扩展以提供默认实现之外的功能
》遵守协议以提供标准化的功能
此外,类具备一些结构体不具备的特性,如:
》继承允许一个类继承自另一个类
》类型投射可以在运行时检查并且影响类实例的类型
》析构方法允许实例释放它被分配的任何资源
》引用计数允许多个引用指向一个类实例 (结构体在Swift中传递时总是拷贝传递的,不使用引用计数)
语法
类与结构的定义语法类似,用关键class(或struct)加类名(或结构体名)后面跟一对大括号。比如:
struct Resolution { var width = 0 var height = 0 } class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String? }
类与结构体的实例
创建类和结构体的实例语法非常类似:
let someResolution = Resolution() let someVideoMode = VideoMode()
获取属性
用点语法获取类或结构体实例的属性值,比如:
println("The width of someResolution is /(someResolution.width)") // prints "The width of someResolution is 0” someVideoMode.resolution.width = 1280
注意:和OC不一样,Swift允许你给结构体的属性的子属性直接赋值,而不要把整个属性替换为新值。
结构体的逐成员初始化方法
当定义结构体的时候,它会自动生成一个逐成员的初始化方法用来初始化新实例的成员属性,比如:
let vga = Resolution(width: 640, height: 480)
类并没有这个特性。
结构体和枚举及Swift中所有基本类型都是值类型,当它们被赋值给常量、变量、或者被传递给函数的时候,值都会被拷贝。而类是引用类型的,当被赋值给常量、变量、或者被传递给函数的时候,它们并不会拷贝一份,而是直接传递指向这个实例的同一引用。
身份运算符
因为类是引用类型,因此,可能会存在多个常量或者变量指向同一个实例。此时会需要确认多个常量或者变量是否是指向的完全同一个实例,因此,Swift提供了两个身份操作符:
身份相同 (===)
身份不同 (!==)
注意这个身份相同运算符(===)和相等运算符(==)是不一样的,它意味着两个常量或者变量指向的是完全相同的一个实例。
指针
C/C++/OC中都是使用指针来标识内存中的地址,Swift中指向实例的常量/变量类似于这种指针,但它不是直接指向内存地址的指针,也不需要你在它之前写星号(*)来标识你正在创建一个引用。
选择类还是结构体
类和结构体都是用来定义自定义数据类型的,结构体是值类型,按值传递的,类是引用类型的,按引用传递的,一般情况下,以下情况应该考虑创建结构体:
》创建这个结构的主要目的是包含一些相关联的简单数据值
》有理由在将结构赋值给变量/常量的时候需要拷贝它的值而不是直接传递引用
》结构存储的任何属性都是期望进行值拷贝的它们自己值类型
》结构不需要从已有类型中继承属性或者行为
其他情况下,定义一个类,然后创建它的实例。
Swift的String、Array、Dictionary都是用结构体实现的,因此是值类型,这和OC中的NSString、NSArray、NSDictionary是不一样的,后者都是用类实现的,因此是引用类型。
注意:值类型在传递过程中是会拷贝值,但并不代表每次都会发生真实的拷贝,只是意义上的拷贝,Swift会决定是否真的需要拷贝它的值。
属性
属性将值关联到特定的类、结构、枚举。
存储属性将常量/变量作为实例的一部分存储起来,而计算属性则是计算(而非存储)值。计算属性可以由类、结构、枚举提供,而存储属性只能由类和结构提供。
存储属性和计算属性一般都是和特定类型的实例相关联的,但是,属性其实也可以和类型本身相关联,就是所谓的类型属性。
此外,还可以自定义观察者来监听属性值的改变,属性观察者可以被添加到你自定义的存储属性以及子类从它父类继承得到的属性。
存储属性
最简单的形式,就是作为类或者结构体的实例的一部分存储起来的常量/变量。你可以在定义存储属性的时候就提供默认值,并且可以在初始化的时候设定和改变存储属性的值,即使存数属性是常量的。
如下的例子中,定义了一个结构体,描述一个定长范围,一旦它的实例被创建,那么其length属性就不可更改:
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
在上面的例子中,length属性是一个常量,在初始化的可以被设定和更改,在那之后就不能再被更改了。
常量结构体实例的存储属性
如果你创建了一个结构体实例并将其赋值给一个常量,那么它的所有属性都不能再被更改,即使该属性是以变量形式定义的。
这主要是因为结构体是值类型的,当一个实例被标记为常量的时候,它的所有属性就都被标记为常量了。而类就不是这样,它是引用类型,当把类实例赋值给常量时,它的变量属性仍然是可以更改的。
懒存储属性
懒存储属性是指在初始化的时候并不提供值,直到第一次使用的时候才提供。通过在定义之前加上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") //作为manager属性的DataImporter实例并没有被创建
一直到需要使用这个属性的时候,它的初始值才会被计算出来:
println(manager.importer.fileName) // DataImporter 实例被创建 // prints "data.txt”
存储属性和实例变量
熟悉OC的人应该知道有两种方式将值和引用存储为类实例的一部分。除了属性之外,还可以用实例变量作为属性存储的值的后备存储。
Swift将这两个概念统一到了属性定义中,一个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))") // prints "square.origin is now at (10.0, 10.0)”
这里的center属性就是计算属性,因为它的值是可以通过origin和size计算出来的,因此不需要存储为显示Point型值。这个属性依然是通过点属性获取的,不过此时是触发了getter的调用以获取值。设定新值的时候也是类似的过程。
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) } } }
如果计算属性只有getter而没有setter的时候,只能获取它的值而不能设定或者改变它的值,此时成为只读计算属性。
计算属性(包括只读计算属性)只能用var来定义,因为他们的值并不是确定的。let只用来定义那些在初始化之后值明确地不能改变的属性。
只读计算属性的定义可以简写为省略get关键字及其大括号:
struct Cuboid { var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth } }
属性观察者
属性观察者监听并响应属性值的变化,只要属性的值被新设定,观察者都会被调用,即使设定的新值等于原来的值。
你可以给任意存储属性添加观察者,懒存储属性除外。
你也可以通过在子类中重载属性,给任意继承过来的属性(包括计算属性)添加观察者。你不需要给非继承的计算属性添加观察者,因为你可以直接在它的setter里边处理这些事情。
你可以选择性地给属性添加以下观察者:
willSet:在新值被存储之前触发,会传递进一个常量代表新的值,如果你在willSet实现中没有指定参数名称,会使用默认名称newValue
didSet:新值被存储之后立刻触发,会传递进一个常量代表新的值,如果你在willSet实现中没有指定参数名称,会使用默认名称oldValue
注意:如果值的改变是在初始化的时候,这两个监听都不会被触发。
class StepCounter { var totalSteps: Int = 0 { willSet(newTotalSteps) { println("About to set totalSteps to /(newTotalSteps)") } didSet { if totalSteps > oldValue { println("Added /(totalSteps - oldValue) steps") } } } }
全局变量和局部变量同样具有前面介绍的属性的计算和观察能力,前面介绍的所有全局变量都是存储变量,像存储属性一样,它们存储特定类型的值,并允许被设定和获取。
也可定义计算变量或者给存储变量添加观察者,全局变量或者局部变量都可以。
全局常量和变量都是懒计算的,像懒存储属性一样,不过它们不需要添加lazy关键字。局部变量和常量都不是懒计算的。
类型属性
实例属性是指那些被特定类型的实例拥有的属性。每次你创建这个类型的新实例,它就拥有自己的属性值,和其他实例是分开的。
你也可以定义属于某个类型的属性,它不属于任何一个这种类型的实例。无论创建多少实例,这种属性只有一份。
类型属性适合定义那些所有实例都通用的属性。比如所有实例都能用的常量属性(像C中的静态常量),或者所有实例都能访问的变量属性(像C中的静态变量)。
对值类型(结构和枚举),你可以定义计算式和存储式类型属性,对类而言,你只能定义计算式类型属性。存储式类型属性可以是常量和变量,计算式类型属性只能是变量,这和实例属性是一样的。
注意:和存储式实例属性不一样,你必须给存储式类型属性设定默认值,因为类型本身没有初始化方法。
在C和OC中,通过定义为全局静态(static)变量来定义和类型关联的静态常量和变量。Swift中,类型属性是作为类型定义的一部分写在类型外部大括号之内的,并且每个类型属性都是显示地限定了它支持的类型。
通过static关键字定义类型属性,对于类类型的计算式类型属性,可以用class关键字代替,以便子类可以重载父类的实现,一下示例展示了存储式和计算式类型属性的语法:
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } enum SomeEnumeration { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } class SomeClass { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } class var overrideableComputedTypeProperty: Int { // return an Int value here } }
示例中的计算式属性是用的只读的简写形式,没有setter,当然这不是必须的。
查询和设置类型属性
可实例属性一样,类型属性的查询和设置通过点属性,不过它是在类型本身上操作,不是该类型的实例。比如:
println(SomeClass.computedTypeProperty) // prints "42" println(SomeStructure.storedTypeProperty) // prints "Some value." SomeStructure.storedTypeProperty = "Another value." println(SomeStructure.storedTypeProperty) // prints "Another value.”