方法
方法是和特定类型结合在一起的函数。类、结构体和枚举都可以i定义一个实例方法,方法中封装了针对给定类型的实例的特定的功能。类、结构体和枚举同样可以定义类型方法,类型方法和类型自身结合在一起。类型方法和OC中的类方法类似。
Swfit中结构体和枚举可以定义方法,与C、OC比较,这是个重大的区别。OC中,类是唯一可以定义方法的类型。Swift中,有更多的选择:类、结构体、或者枚举。
实例方法(Inistance Methods)
实例方法是属于特定类行(类、结构体、枚举)实例的函数。实例方法或通过提供访问或者修改实例属性的途径,或通过提供和和实例相关的功能来支撑实例。实例方法的语法和函数的语法完全一致,参见函数一章。
在类型的两个花括号之间可以定义实例方法。一个实例方法默认可以访问这个类型的实例的全部实例方法和属性。一个类型方法只能被它所在的类型实例调用。没有类型实例,类型方法不能被调用。
这里有一个例子,定义了一个简单的Counter类型,它可以被用来计算时间发生的次数:
class Counter { var count = 0 func increment() { count++ } func incrementBy(amount: Int) { count += amount } func reset() { count = 0 } }
Counter类定义了三个实例方法:
1:increment将计数器加1;
2:incrementBy(amount:int)给计数器增加一个指定的整形数;
3:reset将计数器清零。
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
方法的本地和外部参数名称
函数的参数可以同时有内部名字(在函数体内使用的)和外部名字(在函数被调用时使用),参见 External Parameter Names。方法也是一样的,因为方法仅仅是和类型绑定在了一起的函数。然而,内部和外部参数名字的默认行为会和函数不同。
Swift的函数和OC中的函数很像。像在OC中,Swift中方法的名字通常用一个介词(比如with、for 、by)指向方法的第一个参数,就像Counter类中的incrementBy方法一样。介词的作用是在调用方法的时候便于理解方法的含义(像句子一样被阅读)。Swift makes this established method naming convention easy to write by using a different default approach for method parameters than it uses for function parameters.
Swift默认给方法的第一个参数名称一个内部参数名称,默认同时给第二个以及接下来的参数名称内部名称和外部名称 。这个约定贴合典型的命名和调用约定,就像在写OC中的方法一样,使得调用函数无再限定参数名称。
比较一下另一个版本的Counter类,它定义了一个更 复杂的incrementBy 方法:
class Counter { var count: Int = 0 func incrementBy(amount: Int,numberOfTimes: Int) { count += amount * numberOfTimes } }
这个incrementBy方法有两个参数:amount和numberOfTimes。默认的,amount只有内部参数名称,但是numberOfTimes则同时有本地参数名称和外部参数名称。你可以这样调用这个方法:
let counter = Counter() counter.incrementBy(5,numberOfTimes: 3) // counter value is now 15
可以不必定义第一个参数值的外部参数名称,因为对于函数incrementBy它的意义是明确的。不同的是,第二个参数被使用外部参数名限定了,为的是使得它的意义明确 。
对这个方法实际的处理行为和你在numberOfTimes参数前加了#一样:
func incrementBy(amount: Int,#numberOfTimes: Int) { count += amount * numberOfTimes }
上面描述的默认行为意味着方法定义方面,Swift和OC有着一样的语法风格,调用时自然而且传神。
变更方法的外部参数名行为
有时给方法的第一个参数添加外部参数名是必要的,尽管默认是不这样做的。你可以明确给出一个外部参数名也可以在第一个参数名前面添加#前缀,将内部参数名同时也作为外部参数名。
相反,如果你不想给第二或者第二个之后的参数提供外部参数名,使用_替代默认的行为。
自属性
每个类型的实例都有一个默认的属性叫做self,实际上它和实例自身是一样的。在实例方法内可以用self属性指代当前实例。
上面的increment方法可以这样写:
func increment() { self.count++ }
实践中,你不必每次都写上self。如果没写的化,每次你在方法内使用已知的属性或者方法,Swift会认为你指代的是当前实例自身的属性或方法。前面的Counter的三个实例方法证明了这些。
但当一个实例方法的参数和实例本身的属性同名时,这个规则不再适用。这种情况下,参数名称有优先权,提及属性就必须要与另外的方式。可以使用self来区分参数和属性。
这里,self将一个方法的参数x和一个实例属性也叫做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前缀,Swift会认为所有的x都是参数。
在实例方法中修改值类型
结构体和枚举是值类型的。默认的,值类型的属性无法通过它的实例方法进行修改。
然而,如果需要在一个特定的方法中修改结构体或者枚举的属性,你可以选择变异这个方法。变异方法可以在方法内可以修改属性,而且任何做过的修改在方法结束后会写回到原始的结构中。变异方法也可以分配一个新的实例给它隐含的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)"
上述Point结构体定义了一个mutating方法:moveByX,这个方法用一定数据移动一个Point实例。没有返回一个新的点,这个方法实际上修改了这个点。mutating 关键字添加在前面,使得它可以修改属性。
记住:一个结构体常量的一个变异方法不能被调用,因为它的属性不恩那个被修改,即使属性是变量也不行,这在 Stored Properties of Constant Structure Instances 有描述:
let fixedPoint = Point(x: 3.0,y: 3.0) fixedPoint.moveByX(2.0,y: 3.0) // this will report an error
变异方法中给self赋值(Assigning to self Within a Mutating Method)
变异方法可以将完整的一个新实例复制给隐含的self。上述的Point例子可以这样写:
struct Point { var x = 0.0,y deltaY: Double) { self = Point(x: x + deltaX,y: y + deltaY) } }
这个版本的变异方法moveByX创建了一个新的结构(新结构中的x和y都是目标数据了)。这两个版本的方法运行结果是一样的。
枚举的变异方法可以将隐藏的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
遮盖力例子定义了一个与三个状态开关的枚举。这个开关在三个状态(Off,Low,High)间来回切换,每次切换他的next方法都被调用。
类型方法
上面描述的实例方法,是特定类型实例上被调用的方法。同样可以定义类自身上被调用的方法。这类方法被称为类型方法。对于类,你可以在func关键字前使用class关键字、对于结构体和枚举你可以在关键字func前使用static关键字来定义类型方法。
NOTE
在OC中,你只可以给OC的类定义类型级别的方法。Swift中,可以给类、结构体、和枚举定义类型级别的方法。每个类型方法作用于类型支持的范围。
类型方法调用时采用点号,就像实例方法一样。然而,调用时不是调用具体实例的而是类型的。这里展示了如何调用一个叫做SomClass的类的类型方法:
class SomeClass { class func someTypeMethod() { // type method implementation goes here } } SomeClass.someTypeMethod()
在类型方法中,隐含的self属性指向类型自身,而不是类型的实例。对于结构体、枚举,这意味着你可以使用self来区分静态属性和静态方法参数,就像区分实例属性和实例方法参数一样。
通常的,在类型方法中使用的任何没有限定名的方法和属性名字将会指向类型级别的方法或者属性。一个类型方法可以用方法名字调用另一个类型方法,不需要加上类型的前缀。类似的,结构体和枚举的类型方法可以不用类型名字前缀访问静态属性,仅仅通过静态属性的名字。
下面的例子定义了一个叫做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 } } }
LeverTracker结构体记录了所有玩家没有解锁的最高级别,这个值被存储到一个静态属性中叫做highestUnlockedLevel。
LevelTracker 同样定义了两个类型方法处理highestUnlockedLevel 属性。第一个类型方法叫做unlockLevel,每当一个新级别被解锁它会修改highestUnlockedLevel 的值。第二个类型方法叫做levelIsUnlocked,它会在参数值代表的等级已经被解锁的情况下返回true。(记住:类型方法可以访问静态属性highestUnlockedLevel ,不必要写成LevelTracker.highestUnlockedLevel)
除了静态属性和类型方法,LevelTracker记录了玩家的进度。使用currentLevel记录当前玩家的进度。
为了管理currentLevel属性,LevelTracker定义了一个实例方法叫做advanceToLevle。在更新currentLevle之前,这个方法检查请求的方法是否已经解锁。advanceToLevel方法返回一个布尔值表示是否实际上修改了currentLevel。
下面,在Player类中使用了LevelTracker 结构体,来记录单个玩家的进度:
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方法,当玩家完成了一个特定的关卡,这个方法就被调用。这个方法解锁了所有玩家的下一级别同时更新当前玩家的进度。(advanceToLevel返回的布尔值被忽略了。因为这句之前调用LevelTracker.unlockLevel已经将将已知的级别解锁了。)
你可以为了一个新的玩家创建一个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"