类与结构体app
Swift中,并不要求把自定义类或结构的接口和实现写在不一样的文件中。你在一个文件中定义类或结构体,那么这个类或结构体的外部接口就自动能够在其余代码中使用了。ide
类和结构有不少类似和区别,相同点有:函数
》能够定义属性来存储值idea
》能够定义方法提供各类功能spa
》能够定义下标使得能够经过下标语法获取值指针
》能够定义初始化方法来设定初始状态code
》能够被扩展以提供默认实现以外的功能blog
》遵照协议以提供标准化的功能继承
此外,类具有一些结构体不具有的特性,如:接口
》继承容许一个类继承自另外一个类
》类型投射能够在运行时检查而且影响类实例的类型
》析构方法容许实例释放它被分配的任何资源
》引用计数容许多个引用指向一个类实例 (结构体在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.”