注:本文为自己学习The Swift Programming Language的笔记,其中的例子为引用原书和其他博文或自己原创的。每个例子都会批注一些实践过程中的经验或思考总结。
1.基础
方法就是和类型相关的函数,类、结构体和枚举都可以定义实例方法和类方法。与实例属性和类属性的区别一样,实例方法是属于实例的一个完成特定功能的函数,而类方法是属于类的。结构体和枚举能定义方法是Swift和其他类C语言最大的不同点之一。
2.实例方法
实例方法是属于特定类、结构体和枚举的实例的函数,可以实现获取或修改实例属性、提供与类的目的相关的功能等。
在类型定义内部,实例方法可以隐式的被该类型的其他的实例方法或实例属性所调用;而在类型定义外部,实例方法只能被某一个该类型的实例调用,而不能在没有实例的情况下直接调用。
实例方法的定义如同普通的函数,func关键字打头。下面定义一个计数类counter:
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
它有两个函数increment和incrementBy,实例方法的定义可以参见函数定义。调用实例方法,先要创建一个类的实例:
let counter = Counter()
counter.incrementBy(100)
counter.reset()
2.1实例方法的局部和外部参数名
和函数一一样,方法的参数也可以有局部参数名[local parameter]和外部参数名[external parameter],但是他们的默认行为和函数不同。
为了使方法在被调用时的代码读起来就像一句话一样,Swift方法命名规范沿用的Objective-C中加介词的方法,其中包括了with、for和by等。与Objective-C把介词放在第一个参数外部参数名内不同的是,Swift采用不同的默认行为来解决这个问题。Swift默认第一个参数只有局部变量名,而后面其他的参数都有默认的外部和局部参数名。
对Counter类中的incrementBy方法重载:
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func incrementBy(amount: Int, numberOfTimes : Int) {
count += amount * numberOfTimes
}
func reset() {
count = 0
}
}
那么在调用时,第一个参数不带外部参数名,第二个参数带上默认和内部参数名一样的外部参数名,这样读起来的确像一句通顺的话了:
counter.incrementBy(5, numberOfTimes : 3)
默认的第二个参数外部参数名让方法在调用时目的性更加明确。
我们也可以添加#号符号显式的区别这一个默认行为,我们也可以显式的修改外部参数名,也能用下划线 _ 符号显式的表示第二个及以后的参数不带外部参数名。
2.2self属性
每一个类型的实例都有一个叫做self的隐式属性,它等价于这个实例本身。self属性配合点符号使用。
由于编译器可以判断指代,一般情况下不用显式的给出self + . 符号 + 属性/方法,而是直接调用属性/方法。除非,这个实例方法的参数名和这个实例的某些属性名相同时,self才排上用场(编译器不能判断指代时):
struct Point {
var x = 0.0, 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")
}
这里实例方法参数名x和实例属性名x一样,用self加以区别。其实self较为常用的时候是在init构造函数中:
init(x : Double, y : Double) {
(self.x, self.y) = (x,y)
}
不过这个构造函数时默认的构造函数。
2.3实例方法修改值类型
值类型的实例方法不能直接修改属于值类型的属性,Swift使用mutating关键字实现。
在实例函数func之前添加mutating关键字,表明这个函数可以修改值类型,给Point结构体添加moveByX方法:
mutating func moveByX(deltaX : Double, deltaY : Double) {
x += deltaX
y += deltaY
}
注意常量实例不能调用mutating methods。除了这样分别的修改属性,也可以利用隐式属性self直接赋值一个全新的实例,比如上面的方法可以等价写成:
mutating func moveByX2(deltaX : Double, deltaY : Double) {
self = Point(x : x + deltaX, y : y + deltaY)
}
3.类型方法
类型方法中的隐式属性self指的是类型本身,而不是某个实例。
一个类型方法可以在内部调用其他的类型方法,不用添加self前缀或者类型名称和点符号前缀;也可以修改和访问类型属性。实例方法当然也可以调用类型方法,但要给出类型名和点符号前缀的完整形式。
下面定义一个LevelTracker结构体来记录玩家玩游戏解锁关数,设定为玩家一级一级顺序解锁下一关。它有一个类型属性来记录最高解锁关数,一个类型方法解锁和一个类型方法判断某关是否被锁住。实例属性记录当前关数,然后一个可变函数实现向某一关卡迈进的行为:
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
}
}
}
之所以这么定义这个结构体,是因为当前关数和当前过关是某一个实例的记录和行为,而解锁关数是属于这个游戏本身的,而不是某个游戏的实例。
游戏设定为支持多用户的单人游戏,因而定义玩家Player类,它有一个过关记录tracker:
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
}
}
玩家在完成一个关卡后调用completeLevel方法,先更新最大解锁关数,在进行进入下一关动作。
创建一个Player实例,当他完成关卡1后立即解锁关卡2:
var player1 = Player(name: "JCGuo")
player.completedLevel(1)
println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// prints "highest unlocked level is now 2
这时候如果创建另一个玩家实例,他想玩第6关,就会返回第6关被锁住的信息:
var player2 = Player(name: "John Snow")
if player2.tracker.advanceToLevel(6) {
println("player2 is now on level 6")
} else {
println("level 6 has not yet been unlocked")
}
// prints "level 6 has not yet been unlocked