方法(Methods)
方法是与某些特定类型相关联的功能/函数。
类、结构体、枚举都可以定义实例方法;实例方法为制定类型的实例封装了特定的任务与功能。
类、结构体、枚举也可以定义类(型)方法(type itself);类型方法与类型自身相关联。
类型方法与OC中的类方法(class methods)相似。
在Swift中,结构体和枚举能够定义方法;事实上这是Swift与C/OC的主要区别之一。
在OC中,类是唯一能定义方法的类型。在Swift中,你能够选择是否定义一个类/结构体/枚举,并且你仍然享有在你创建的类型(类/结构体/枚举)上定义方法的灵活性。
实例方法(Instance Methods)
实例方法是某个特定类、结构体或者枚举类型的实例的方法。实例方法支撑实例的功能;或者提供方法,以访问和修改实例属性;或者提供与实例的目的相关的功能。实例方法的语法与函数完全一致。
实例方法要卸载它所属的类型的前后括号之间。实例方法能够访问他所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的特定实例调用。实例方法不能被孤立于现存的实例而被调用。
class Counter{
var count = 0
func increment(){
count++
}
func incrementBy(amount: Int){
count += amount
}
func reset(){
count = 0
}
}
· increment 让计数器按1递增
· incrementBy(amount: Int)让计数器按一个指定的整数值递增
· reset 将计数器重置为0
和调用属性一样,用点语法调用实例方法:
let counter = Counter()
println("the counter is \(counter.count)")
counter.increment()
println("the counter is \(counter.count)")
counter.incrementBy(5)
println("the counter is \(counter.count)")
counter.reset()
println("the counter is \(counter.count)")
//the counter is 0
//the counter is 1
//the counter is 6
//the counter is 0
方法的局部参数名称和外部参数名称(Local and External Parameter Names for Methods)
函数参数有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用)。
对于方法参数也是这样,因为方法就是函数(只是这个函数与某个类型相关联),但是,方法和函数的局部名称和外部名称的默认行为是不一样的。
Swift中的方法和OC中的方法及其相似。像再OC中,Swift中方法的名称通常用一个介词指向方法的第一个参数,比如:with,for,by等等。介词的使用让方法在被调用时能像一个句子一样被解读。Swift这种方法命名约定很容易落实,因为它使用不同的默认处理方法参数的方式,而不是用函数参数来实现。
具体来说,Swift默认仅给方法的第一个参数名称一个局部参数名称;但是默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相匹配,这与你在写OC的方法时很相似。这个约定还让expressive method调用不需要再检查/限定参数名。
class Counter{
var count: Int = 0
func incrementBy(amount: Int,numberOfTimes: Int){
count += amount * numberOfTimes
}
}
let count = Counter()
count.incrementBy(5,numberOfTimes: 5)
println(count.count)
//25
incrementBy方法有两个参数:amount 和 numberOfTimes。默认,Swift只把amount当做一个局部名称,但是把numberOfTimes即看做本地名称又看做外部名称。
你不必为第一个参数值在定义一个外部变量名:因为从函数名incrementBy已经能很清楚的看出它的目的/作用。但是第二个参数,就要被第一个外部参数名称所限定,以便在方法被调用时让他作用明确。
这种默认行为能够有效的检查方法,比如在参数numberOfTimes前写了个#时:
class Counter{
var count: Int = 0
func incrementBy(amount: Int,#numberOfTimes: Int){
count += amount * numberOfTimes
}
}
这意味着:在Swift中定义方法使用了与OC同样的语法风格,并且方法将以自然表达式的方式被调用。
修改外部参数名称(Modifying External Parameter Name Behavior for Methods)
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你可以自己添加一个明确的外部名称;你也可以用一个hash符号作为第一个参数的前缀,然后用这个局部名字作为外部名字。
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,你可以通过使用下划线(_)作为该参数的显式外部名称来覆盖默认行为。
self属性(The Safe Property)
类型的每一个实例都有一个一个隐含属性叫做self,它完全等同于这个实例变量本身。你可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例。
class Counter{
var count: Int = 0
func increment(){
self.count++
}
}
let number = Counter()
number.increment()
println(number.count)
//1
实际上,你不必在你的代码里面经常写self。不论何时,在一个方法中使用一个已知的属性或者方法名称,如果你没有明确的写self,Swift嘉定你是指当前实例的属性或者方法。
这条规则的主要例外发生在当实例方法的,某个参数名称与实例的某个实例的某个属性名称相同时。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更恰当的方式。你可以使用隐藏的self属性来区分参数名称和属性名称。
struct Point{
var x = 0.0
var y = 0.0
func isToTheRightOfX(x: Double) -> Bool{
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0){
println("This point is to the right of the line where x == 1.0")
}
//This point is to the right of the line where x == 1.0
在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法。方法可以从内部变异它的属性;并且它做的任何改变在方法结束都会回写到原始结构。方法会给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
要变异方法,将关键字mutating 方法方法的func关键字之前就可以了。
struct Point{
var x = 0.0
var 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("This point is now at (\(somePoint.x),\(somePoint.y))")
//This point is now at (3.0,4.0)
上面的Point结构体定义了一个变异方法(mutating method)moveByX,moveByX用来移动一个point。moveByX方法在被调用时修改了这个point,而不是返回一个新的point。方法定义是加上mutating关键字,所以方法可以修改值类型的属性。
注意:不能在结构体类型的常量上调用变异方法,因为常量的属性不能被改变,就算你想改变的是常量的可变属性也不行。
在变异方法中给self赋值(Assigning to self Within a Mutating Method)
变异方法能够赋给隐含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)
}
}
var somePoint = Point(x: 2.0, y: 3.0)
somePoint.moveByX(1.0,y: 1.0)
println("This point is now at (\(somePoint.x),\(somePoint.y))")
//This point is now at (3.0,4.0)
新版的变异方法moveByX创建了一个新的分支结构。调用这个版本的方法和调用上个版本的最终结果是一样的。
枚举的变异方法可以让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 owenLight = TriStateSwitch.Low
owenLight.next()
类型方法(Type Methods)
实例方法是被类型的某个实例调用的方法。你也可以定义类列本身调用的方法,这种方法叫做类型方法。声明类的类型方法,在方法的func关键字前加上关键字class;声明结构体体和枚举的类型方法,在方法的func关键字之前加上关键字static。
注意:在OC里,你只能为OC的类定义类型方法(type-level methods)。在Swift中,你可以为所有的类、结构体和枚举定义类型方法。
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。
class SomeClass{
class func someTypeMethod(){
//type method implementation goes here
}
}
SomeClass.someTypeMethod()
在类型方法的方法体(body)中,self指向这个类型本身,而不是类型的某个实例。对于结构体和枚举来说,这意味着你可以用self来消除静态属性和静态方法参数之间的二义性(类似上面处理实例属性和实例方法参数的情况)
一般的,在类型方法里面所使用的任何未限定的方法和属性名称,将会指向其他的类型级别的方法和属性。一个类型方法可以用另一个类型方法的名称调用它,而无需在方法名称前面加上类型名称的前缀。同样,结构体和枚举的类型方法也能够直接通过静态属性的名称访问静态属性,而不需要类型名称前缀。
struct LevelTracker{
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int){
if level > highestUnlockedLevel{
highestUnlockedLevel = level
}
}
static func levelIsUnlocked(level: Int) -> Bool{
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool{
if LevelTracker.levelIsUnlocked(level){
currentLevel = level
return true
}else{
return false
}
}
}
class Player{
var tracker = LevelTracker()
let playerName: String
func completedLevel(level: Int){
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String){
playerName = name
}
}
Player类创建一个新的LevelTracker实例来检测这个用户的发展进度。
提供的completedLevel方法:一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。
var player = Player(name: "Cyndi")
player.completedLevel(1)
println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
//highest unlocked level is now 2
player = Player(name: "Beto")
if player.tracker.advanceToLevel(6){
println("player is now on level 6")
}else{
println("level 6 has not yet been unlocked")
}
//level 6 has not yet been unlocked