Swift学习笔记十二
方法
方法就是和某种特定类型相关联的函数。类、结构体、枚举都可以定义实例方法和类型方法。类型方法和OC中的类方法类似。
结构体和枚举也可以定义方法是Swift与C/OC之间很大的一个区别,在OC中,只有类才能定义方法。
实例方法
实例方法是从属于某个类实例或结构体实例、枚举实例的方法。他们提供与该实例相关的功能,比如获取更改属性或者提供其他函数功能。实例方法的语法和函数完全相同。
实例方法隐式地可以访问该类型的属性和方法。只能从该类型的实例上调用实例方法。比如:
class Counter { var count = 0 func increment() { count++ } func incrementBy(amount: Int) { count += amount } func reset() { count = 0 } } let counter = Counter() // the initial counter value is 0 counter.increment() // the counter's value is now 1 counter.incrementBy(5) // the counter's value is now 6 counter.reset() // the counter's value is now 0
方法的内部和外部参数名称
如前所述,函数的参数可以同时拥有内部名称和外部名称,方法也是如此。不过,实例方法的内部和外部名称的默认行为和函数有所不同。
像OC一样,Swift中方法的名字一般都是用一个介词如with、for、by和第一个参数名称接应起来的。比如上个例子中的incrementBy(_:),介词使得方法在被调用的时候可读性更高,就像一个连贯的句子一样。因此Swift通过给方法参数提供了不同于函数的默认行为来使得这种方法命名方式更加便利:Swift给方法的第一个参数名提供了内部参数名,给第二个及以后的所有参数名同时提供了内部和外部参数名。
依旧以上一个Counter为例子,假设它的实例方法incrementBy需要两个参数:
class Counter { var count: Int = 0 func incrementBy(amount: Int, numberOfTimes: Int) { count += amount * numberOfTimes } }
默认情况下,Swift把第一个参数名amount只看做内部参数名,而把numberOfTimes同时看做内部和外部变量名。因此,在调用这个实例方法的时候:
let counter = Counter() counter.incrementBy(5, numberOfTimes: 3) // counter value is now 15
不需要给方法的第一个参数定义外部参数名,因为它的意义通过方法名incrementBy可以很明确地表现出来,但是后边的参数都需要外部参数名使得在方法调用的时候语义非常明确。这种默认的处理方式就像你在方法定义的时候已经在第二个及以后的每个参数名称前加上了(#)符号。
当然,如果你希望改变这种默认行为,给第一个参数指定外部参数名,也可以用通常的方法指定,如果你不想给第二个及以后的参数添加默认外部参数名,那么可以在参数名前加上下划线(_)作为外部参数名。
self属性
每个实例对象都有一个隐式属性self,它指向实例对象本身。在实际开发中,并不需要经常写self,在实例方法内部,如果你不显示地写明self,每次你在使用一直的属性或方法时,Swift都假定你是在用当前对象的属性或方法。这个原则的一个主要例外是当方法的参数名和实例对象的属性名相同时,方法的参数名会具有较高的优先级,如果需要使用实例对象的属性,就需要使用self。
从实例方法内改变值类型
结构体和枚举都是值类型的,默认情况下,值类型的属性是不能从它的实例方法内部改变的。
但是,如果你希望从一个特殊的方法里边改变结构体或枚举的属性值,你可以选择改变那个方法的行为。然后方法就可以从内部改变它的属性,并且它产生的任何改变在方法结束后都会写到原始的结构体上。方法还可以给它的隐式属性self赋一个全新的实例对象,当方法结束时,这个新实例将取代现有的那个。
通过在func前加上关键字mutating来实现上面的行为:
struct Point { var x = 0.0, y = 0.0 mutating func moveByX(deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY } } var somePoint = Point(x: 1.0, y: 1.0) somePoint.moveByX(2.0, y: 3.0) println("The point is now at (\(somePoint.x), \(somePoint.y))") // prints "The point is now at (3.0, 4.0)”
上面的示例中,定义了一个mutating方法moveByX,它的目的是将点对象移动一定距离,它是在当前的点对象上直接操作的,而不是返回一个新的点对象。
注意,如果你将值类型实例对象赋值给了一个常量,那你就不能调用它的mutating方法了,因为一旦赋值给常量,它的所有属性就都不能改变了,即使该属性是变量属性。
从Mutating方法内部给self赋值
Mutating方法可以给隐式属性self赋一个全新的对象,比如上面的例子可以改写成:
struct Point { var x = 0.0, y = 0.0 mutating func moveByX(deltaX: Double, y deltaY: Double) { self = Point(x: x + deltaX, y: y + deltaY) } }
这个方法是返回了一个全新点对象实例,不过当方法完成时,结果和前面的例子是完全一样的。
枚举类型的Mutating方法可以将隐式属性self设定为这个枚举的另一个不同的成员:
enum TriStateSwitch { case Off, Low, High mutating func next() { switch self { case Off: self = Low case Low: self = High case High: self = Off } } } var ovenLight = TriStateSwitch.Low ovenLight.next() // ovenLight is now equal to .High ovenLight.next() // ovenLight is now equal to .Off
类型方法
如前所述,实例方法就是在实例对象上调用的方法。而类型方法是指从类型本身调用的方法。通过在func前面加上关键字static来声明类型方法。类也可以用class关键字来允许子类可以重载父类对该方法的实现。
在OC中,只能在类上声明类型方法,而在Swift中,可以在类、结构体、枚举上声明类型方法。各种类型方法都显示地标明它支持的类型。
class SomeClass { class func someTypeMethod() { // type method implementation goes here } } SomeClass.someTypeMethod()
在类型方法内部,隐式属性self指向类型本身,而非它的实例对象。对结构体和枚举而言,这意味着当类型属性名和方法参数名相同的时候可以用来做区分。
通常情况下,在类型方法里边出现的未定义的方法和属性名字都会指向这个类型的类型属性和类型方法,不需要在属性和方法名字前加类型名称前缀。
下标
下标是获取集合、列表或者队列中元素的简便方法,下标可以通过索引序列(index)来获取和存储值,而不需要额外获取和设置方法。
类、结构和枚举可以定义下标,你可以为一个类型定义多个下标,下标也不局限于一维,可以定义具有多个参数的下标。
下标语法
下标语法通过在实例名称后面写上包含一个或多个值的中括号来使你可以查询实例对象。语法和实例方法语法及计算式属性语法都很像。通过subscript关键字来定义下标,然后像实例方法那样指定一个或多个输入参数及返回类型。和实例方法不同的是,下标可以是只读的活着是读写的。这个行为和计算式属性的getter和setter相似:
subscript(index: Int) -> Int { get { // return an appropriate subscript value here } set(newValue) { // perform a suitable setting action here } }
这个setter和计算式属性类似的,如果你指定了新值参数名,则用那个名字,如果没有指定,默认提供的是newValue。
如果是只读下标,就去掉setter,和计算式属性类似,只有getter时可以省略get关键字用简写形式。
struct TimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index } } let threeTimesTable = TimesTable(multiplier: 3) println("six times three is \(threeTimesTable[6])") // prints "six times three is 18”
下标可以接受任意数目的任意类型的参数,也可以返回任意类型的返回值。下标可以使用变量参数以及可变参数,但是不能使用in-out参数,也不能给参数提供默认值。
类和结构体可以根据需要提供多种下表实现方式。至于哪种方式会被使用,取决于在使用下标的时候,方括号内参数类型,这也叫做下标重载。
下标通常都是接受一个参数,当然,也可以定义为接受多个参数:
struct Matrix { let rows: Int, columns: Int var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(count: rows * columns, repeatedValue: 0.0) } func indexIsValidForRow(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int, column: Int) -> Double { get { assert(indexIsValidForRow(row, column: column), "Index out of range") return grid[(row * columns) + column] } set { assert(indexIsValidForRow(row, column: column), "Index out of range") grid[(row * columns) + column] = newValue } } }
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
继承
一个类可以从另一个类继承方法,属性或者其他特性,继承的类叫子类,被继承的类叫超类。继承是类区别于其他类型的一个主要特征。
类可以访问它超类的属性、方法和下标,也可以提供自己对这些属性、方法和下标的重载版本来改变它们的行为。类也可以给继承的属性添加属性观察者,无论这个属性在超类中是存储式属性还是计算式属性。
任何不继承自其他类的类被称为基类。Swift中的类并没有统一继承自某个全局基类,如果你创建的类没有继承自任何类,它就是基类。
class Vehicle { var currentSpeed = 0.0 var description: String { //这是一个只读的计算式属性 return "traveling at \(currentSpeed) miles per hour" } func makeNoise() { // do nothing - an arbitrary vehicle doesn't necessarily make a noise } }
这里定义了一个没有继承自任何类的基类,它的一个实例方法并没有实现,这是留给子类去具体实现的。
子类的定义是在类名后边加上冒号,然后加上超类的名称:
class Bicycle: Vehicle { var hasBasket = false }
子类默认就继承了超类的特性,比如Bicycle类就自动有currentSpeed属性及makeNoise方法等。当然,子类还可以定义自己的属性和方法,子类也可以被再次继承,子类会依次获得继承链上那些超类的特性。
重载
子类可以给继承来的实例方法、类型方法、实例属性、类型属性及下标提供自己的实现版本,否则它们的实现方式就是继承自超类的。这叫做重载。
在重载实现前面加上关键字override来标明重载某个特性,这表示你是有意要重载某个特性,而不是不小心用了同样的名称或定义。事实上,任何不带override关键字的重载都会在编译时触发错误。override关键字也会让Swift检查重载是否和超类中的特性匹配,以确保重载的定义是正确的。
有时候,在子类重载超类特性的时候,可能需要访问超类对该类的实现,这通过super前缀来访问超类的属性、方法及下标实现。
重载方法就是在方法的func前加上override关键字.
任何继承来的属性都可以被重载,你可以提供自定义的getter,需要的时候也可以提供自定义的setter,无论这个属性在超类中是定义为存储属性或是计算属性。超类中属性究竟是存储式还是计算式子类是无法知道的,它只知道这个属性的名字和类型。你必须同时写明重载的属性的名称和类型以便Swift检查重载是否正确。你可以通过提供getter和setter将一个只读的属性在子类中重载为读写的属性,然后,你不能将一个可读写的属性重载为只读的。
注意:如果你重载的时候提供了setter,那么你必须同时提供getter作为重载的一部分。
class Car: Vehicle { var gear = 1 override var description: String { return super.description + " in gear \(gear)" } }
你可以通过属性重载来给继承来的属性添加观察者,无论这个属性最初是如何实现和定义的,在它发生改变的时候你都可以收到通知。
注意:你不能给继承来的常量存储属性或者只读的计算属性添加观察者,这些属性是不能被改变的,因此在重载时提供willSet和didSet是不合适的。并且,你也不能同时重载一个属性的setter和它的观察者,因为如果你重载了setter,你可以直接在setter里边实现观察者的目的。
class AutomaticCar: Car { override var currentSpeed: Double { didSet { gear = Int(currentSpeed / 10.0) + 1 } } } let automatic = AutomaticCar() automatic.currentSpeed = 35.0 println("AutomaticCar: \(automatic.description)") // AutomaticCar: traveling at 35.0 miles per hour in gear 4
阻止重载
你可以通过标明最终版本来阻止方法、属性或下标被重载,在他们关键字前加上final关键字即可,比如:final var, final func, final class func, final subscript等。
任何试图重载已经标记为final的特性都会触发编译时错误。
你也可以在类关键字class前加上final关键字来将整个类标记为最终版本,任何试图继承final类都会触发编译时错误。