实际上,Swift与C和Objective-C的主要区别就是结构体和枚举可以定义方法。在Objective-C中,只有类才能定义方法。而在Swift中,你可以有选择性的在类、结构体或枚举中定义方法,并且使得在你创建的类中定义方法时更具有灵活性。
实例方法
实例方法是属于一个特定的类、结构体或枚举的实例的函数,函数是实例的支柱,通过提供访问实例属性和修改实例属性的方法,或过过提供相关实例功能的目的。实例方法和函数有相同的语法,就像功能的描述。你写一个带有开和闭的实例方法作为它的所属类型,一个实例方法具有隐式访问所有其他的实例方法和属性类型。一个实例方法只能被它所属的类的特定实例调用。不能因为没有现有的实例和把孤立。
这里定义了一个简单的Counter类,它可以用来计算一个操作发生的次数:
- 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
该Counter类也定义了一个属性变量count,用来维持当前计数器的值的轨道。 你调用实例方法和属性时的语法具有相同点:
- 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
方法的局部和外部参数名
函数的参数可以拥有一个局部名称(在函数体内使用)和一个外部名称(在调用函数时使用),作为外部参数名的描述。方法的参数也是如此,因为方法是与类型相关的函数。然而,局部名称和外部名称的默认行为是不同于函数和方法的。在Swift中方法相对与Objective-C中的方法非常相似,在Objective-C中和在Swift中的方法名的第一个参数使用介词作为参考,或可以通过前面的Counter类的例子的incrementBy方法可以看出来。使用了一个可用的介词便于调用时就像在阅读句子一样方便。Swift使用这种已制定的命名方法使得在书写时更简单,调用方法时使用默认途径比使用函数参数更简洁。
具体来说,Swift在方法中默认的将第一个参数名作为方法的局部参数名,也默认的根据局部参数和外部参数名去给第二个和后续的参数命名。在你书写Objective-c方法时很熟悉这种典型的命名公约和调用约定,并使参数名称更符合你的调用方法的表达。
细想下这个替代版本的Counter类,它定义了一个更复杂的incrementBy方法:
- class Counter {
- var count: Int = 0
- func incrementBy(amount: Int,numberOfTimes: Int) {
- count += amount * numberOfTimes
- }
- }
- let counter = Counter()
- counter.incrementBy(5,numberOfTimes: 3)
- // counter value is now 15
如果你在numberOfTimes参数前写了一个散列符号(#),这种行为则是有效的默认处理方法:
- func incrementBy(amount: Int,#numberOfTimes: Int) {
- count += amount * numberOfTimes
- }
方法的外部参数名称修饰行为
有时为方法的第一个参数提供一个外部参数名是非常有用的。尽管这不是默认的行为。你可以添加一个你自己明确的外部名称,或者你也可以使用局部名称作为外部名称并在参数名前加一个散列符号作为参数名的前缀。相反的,如果你不想为一个方法的第二个参数或后续参数提供外部参数名,可通过使用下划线符号(_)作为该参数的显式外部参数名称来覆盖默认行为。
self 属性
每一个类型的实例都有一个称为 self 的隐式属性,它是完全等同于该实体本身的。你可以使用这个隐式的self属性饮用当前实例的实例方法。在上面的例子中,increment方法也可以写成这样:
- func increment() {
- self.count++
- }
主要的异常发生在一个实例方法的参数名和实例的属性名相同。在这种情况下,参数名优先,有必要参考属性更多的合格方式。你可以使用隐式的self属性区分参数名和属性名。
在这里,使用了self来区分名称同为x的方法参数和一个实例属性:
- 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")
- }
- // prints "This point is to the right of the line where x == 1.0"
修改值类型的实例方法
结构体和枚举都是值类型。默认情况下,值类型的属性不能从它的内部实例方法修改。然而,如果你需要修改你的结构体或枚举的属性在一个特定的方法中,你可以在这个方法中选择加入变异行为。然后该方法可以变异(即改变)它的属性,任何的更改在方法结束时写回原来的结构里。该方法还可以分配一个新的实例到其隐含的self属性,而这个新的实例将取代现有的实例,当该方法结束时。
你可以加入这个行为,通过将mutating关键字写在方法的func关键字前:
- 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)"
请注意,你不能使用一个常量的结构类型去调用变异方法,因为它的属性不能被改变。即使它们使可变的属性,如常量结构体实例的存储属性的描述:
- let fixedPoint = Point(x: 3.0,y: 3.0)
- fixedPoint.moveByX(2.0,y: 3.0)
- // this will report an error
由变异方法分配self
变异方法可以分配一个全新的实例给隐式的self属性。上面所示的Point例子也可以勇下面的方式来替代:
- 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)
- }
- }
枚举的变异方法可以在同一个枚举里为隐式的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 关键字前写上class 关键字来声明类的类型方法,而在结构体或枚举内定义类型方法则需在func关键字前书写static 关键字来声明。@H_414_301@注意:在Objective-C中,你只能为Objective-C类定义类型级方法。在Swift中,你可以为类、结构体和枚举定义类型级方法。每种类型方法由它的类型来明确范围的。
类型方法的调用语法和实例方法的调用方法很像。但是,你只能通过类来调用类型方法,而不是通过这个类的实例来调用。这儿有一个叫做SomeClass的类为您展示了如何调用一个类型方法:
- class SomeClass {
- class func someTypeMethod() {
- // type method implementation goes here
- }
- }
- SomeClass.someTypeMethod()
更为普遍的是,你在一个类型方法体内使用任何不合格的方法和属性名称它都将引用其它类型级的方法和属性。一个类型方法可以通过其它的方法名来调用另一个类型方法,而不需要为类型名加前缀。同样,结构体和枚举的类型方法也能通过使用静态属性名访问静态属性,而不需要类型名做前缀。
下面的例子定义了一个名为LevelTracker结构体,它通过游戏的不同级别或阶段跟踪玩家的进步。这是一个单人游戏,但可以在一个设备上为多个玩家存储信息。当游戏第一次玩时所有的级别都被上锁(除了第一级)。每当一个玩家完成一个级别,该级别将对设备上的所有玩家解锁。该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
- }
- }
- }
LevelTracker还定义了两个类型函数为highestUnlockedLevels工作。首先时一个叫unlockLevel的函数,负责当一个新的级别被解锁时去更新highestUnlockedLevel的值。第二个是一个便利的levelIsUnlocked类型函数,当一个特定的级别被解锁,它返回true。(注意,这些类型方法可以访问highestUnlockedLevel静态属性,你不需要把它写成LevelTracker.highestUnlockedLevel)
除了静态属性和类型方法,LevelTracker游戏通过一个currentLevel实例属性来追踪每个单个玩家当前正在玩的游戏级别。
为了帮助管理currentLevel属性,LevelTracker定义了一个名为advanceToLevel的实例方法。在更新currentLevel之前,该方法会检查新的游戏级别是否已经解锁。advanceToLevel方法通过返回一个bool值来指示是否能够设置currentLevel。
LevelTracker结构体被一个Player类所使用,如下所示,追踪和更新单个玩家的进度:
- 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类创建一个新的玩家实例,看看当玩家完成一个级别会发生什么:
- var player = Player(name: "Argyrios")
- player.completedLevel(1)
- println("highest unlocked level is now (LevelTracker.highestUnlockedLevel)")
- // prints "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")
- }
- // prints "level 6 has not yet been unlocked"