方法
方法
是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与Objective-C中的类方法(class methods)相似。
结构体和枚举能够定义方法是Swift与C/Objective-C的主要区别之一。在Objective-C中,类是唯一能定义方法的类型。但在Swift中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
实例方法
实例方法
是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致。
实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。和调用属性一样,用点语法(dot Syntax)调用实例方法
class Counter { var count = 0 func increment() { ++count } func incrementBy(amount: Int) { count += amount } func reset() { count = 0 } } let c = Counter() c.increment() c.incrementBy(10) c.reset()
方法的局部参数名称和外部参数名称
函数参数可以同时有一个局部名称
(在函数体内部使用)和一个外部名称
(在调用函数时使用)。方法参数也一样。
Swift中的方法和Objective-C中的方法极其相似。像在Objective-C中一样,Swift中方法的名称通常用一个介词指向方法的第一个参数,比如:with
,for
,by
等等。介词的使用让方法在被调用时能像一个句子一样被解读。
具体来说,Swift默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写Objective-C的方法时很相似。这个约定还让富于表达性的方法在调用时不需要再限定参数名称。
class Counter { var count: Int = 0 func incrementBy(amount: Int,numberOfTimes: Int) { count += amount * numberOfTimes } } let c = Counter() c.incrementBy(5,numberOfTimes: 4) print(c.count)
默认情况下,Swift只把amount
当作一个局部名称,但是把numberOfTimes
即看作局部名称又看作外部名称。
你不必为第一个参数值再定义一个外部变量名:因为从函数名incrementBy(_numberOfTimes:)
已经能很清楚地看出它的作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的作用。
在Swift中,定义方法使用了与Objective-C同样的语法风格,并且方法将以自然且富于表达性的方式被调用。
修改方法的外部参数名称
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(_)作为该参数的显式外部名称,这样做将覆盖默认行为。
self属性
类型的每一个实例都有一个隐含属性叫做self
,self
完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的self
属性来引用当前实例。
实际上,你不必在你的代码里面经常写self
。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写self
,Swift假定你是指当前实例的属性或者方法。
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用self
属性来区分参数名称和属性名称。
struct Point {
var x = 0,y = 0
func isTheSameX(x: Int) -> Bool {
print("\(self.x)")
print("\(x)")
return self.x == x
}
func isTheSameX2(x: Int) -> Bool {
print("\(x)")
return x == x
}
}
let p = Point(x: 1,y: 2)
print(p.isTheSameX(3))
print(p.isTheSameX2(3))
/*
1
3
false
3
true
*/
上面的代码中,如果不使用self
前缀,Swift就认为两次使用的x
都指的是名称为x
的函数参数,产生歧义。
在实例方法中修改值类型
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果确实需要在某个特定的方法中修改结构体或者枚举的属性,可以为这个方法选择可变(mutating
)行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的self
属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。要使用可变方法,将关键字mutating
放到方法的func
关键字之前就可以了。
struct Point {
var x = 0,y = 0
mutating func a(m: Int,n: Int) { // 该方法修改了这个点,而不是返回一个新的点。
x += m
y += n
}
}
var a = Point(x: 1,y: 1) // 必须声明为变量!
a.a(5,n: 6)
print(a) // Point(x: 6,y: 7)
不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,
在可变方法中给self赋值
struct Point {
var x = 0,n: Int) { // 创建一个新的实例返回给self
self = Point(x: x + m,y: y + n)
}
}
var a = Point(x: 1,y: 1) // 必须声明为变量!
a.a(5,y: 7)
枚举的可变方法可以把self
设置为同一枚举类型中不同的成员。
enum TriStateSwitch {
case Off,Low,High
mutating func next() -> TriStateSwitch {
switch self {
case .Off:
self = .Low
case .Low:
self = .High
case .High:
self = .Off
}
return self
}
}
var t = TriStateSwitch.Off // 必须声明为变量!
print(t.next()) // Low
print(t.next()) // High
print(t.next()) // Off
上面代码中,开关在不同的电源状态(Off
,Low
,High
)之间循环切换。
类型方法
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做类型方法
(Type Methods)。在方法的func
关键字之前加上关键字static
,来指定类型方法。类还可以用关键字class
来允许子类重写父类的方法实现。
注意,在Objective-C中,你只能为Objective-C的类类型(classes)定义类型方法(type-level methods)。在Swift中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。类型方法和实例方法一样用点语法调用。但是,只能在类型上调用这个方法。
在类型方法的方法体(body)中,self
指向这个类型本身,而不是类型的某个实例。这意味着你可以用self
来消除类型属性和类型方法参数之间的歧义。
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
/** * 文档上的一个名为LevelTracker结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。LevelTracker结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。 */
struct LevelTracker {
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func levelIsLocked(level: Int) -> Bool {
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsLocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
class Player {
var tracker = LevelTracker()
let playerName: String // 这里的初始化和下面的init只能存在一个
func completeLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(playerName: String) {
self.playerName = playerName
}
}
let p1 = Player(playerName: "Tim")
p1.completeLevel(1)
print(p1.tracker.currentLevel) // 2
print(LevelTracker.highestUnlockedLevel) // 2
let p2 = Player(playerName: "Kate")
if p2.tracker.advanceToLevel(10) {
print("解锁成功!")
} else {
print("解锁失败!") // 解锁失败!
}